Service providers
A service provider is responsible for bootstrapping and configuring the services of an Expanse application. A service
provider should be scoped to services that are related to a specific feature or a group of related services. For
instance,
Expanse has a DatabaseServiceProvider
that is responsible for
managing database connections and sessions.
Implementing a service provider
Service providers must inherit from the ServiceProvider
class and implement the register()
method and, optionally, a
boot()
method. There are two import points to remember when implementing a service provider:
- The
register()
method should only be responsible for registering services to the service container. It'd also important to remember that you should not try to retrieve other services from the container here since you have no guarantee that they have been already bound to the container. - The
boot()
method is called after all services have been registered.
Service providers are located in the app/providers
directory.
The register method
The register()
method should only be responsible for registering services to
the service container
Let's see an example with a simplified version of the DatabaseServiceProvider
:
from collections.abc import Generator
from expanse.contracts.database.connection import Connection
from expanse.database.database_manager import DatabaseManager
from expanse.support.service_provider import ServiceProvider
class DatabaseServiceProvider(ServiceProvider):
def register(self) -> None:
self._container.singleton(DatabaseManager)
self._container.scoped(Connection, self._create_connection)
def _create_connection(
self, db: DatabaseManager, name: str | None = None
) -> Generator[Connection]:
connection = db.connection(name)
yield connection
connection.close()
If you are not familiar with service container, you can read more about it
in its documentation, but, in short, the service provider is registering the
DatabaseManager
as a singleton — meaning only one instance will be created during the application lifecycle — and the
Connection
as a scoped service — meaning a new instance will be created for each request.
The boot method
The boot()
method is called after all services have been registered. This is the place where you can interact with
any services you need to provide additional functionality to your application. Let's say you want to add another
function
to your views:
from expanse.support.service_provider import ServiceProvider
from expanse.view.view_factory import ViewFactory
class MyServiceProvider(ServiceProvider):
def boot(self, view: ViewFactory) -> None:
view.register_global(my_new_function=self._my_new_function)
def _my_new_function(self) -> str:
return "Hello"
As you can see, you can type-hint any services in the boot()
method and they will be automatically resolved and
injected.
Registering a service provider
To register a service provider, you need to add it to the providers
list in the config/app.py
file:
providers = [
# Other providers...
"app.providers.my_service_provider.MyServiceProvider",
]