Service Providers¶
Introduction¶
Service providers are the central place of all Arvel application bootstrapping. Your own application, as well as all of Arvel's core services, are bootstrapped via service providers.
But what do we mean by "bootstrapped"? In general, we mean registering things: registering container bindings, event listeners, middleware, and routes. Service providers are the central place to configure your application.
If you open bootstrap/providers.py, you'll see your application's providers list. Each provider gives you a place to wire one slice of your application together.
Writing Service Providers¶
All service providers extend the ServiceProvider class. Most contain a register and a boot method. Generate a new provider with the make:provider command:
A provider receives the Application in its constructor and exposes it as self.app, with the root container as self.container:
from arvel.providers import ServiceProvider
class AppServiceProvider(ServiceProvider):
def register(self) -> None:
self.container.singleton(InventoryService)
async def boot(self) -> None:
...
async def shutdown(self) -> None:
...
The Register Method¶
Within the register method, you should only bind things into the container. The register method is synchronous and runs during the application's build step, before any other provider has booted. You should never attempt to register event listeners, do I/O, resolve another provider's service, or perform any other side effect within the register method — those services may not be bound yet.
The Boot Method¶
The boot method is asynchronous and is called after all other service providers have been registered, meaning you have access to every binding the framework and your app have registered. This is the place for wiring that depends on other services — attaching listeners, registering broadcast channels, opening connections:
from arvel.events.dispatcher import EventDispatcher
async def boot(self) -> None:
# OrderPlaced is your event; SendOrderConfirmation your listener.
dispatcher = self.container.make(EventDispatcher)
dispatcher.listen(OrderPlaced, SendOrderConfirmation)
The Shutdown Method¶
The shutdown method runs on application teardown, in reverse registration order. Release any resources your provider opened:
Provider Members¶
| Member | Purpose |
|---|---|
self.app | The Application. |
self.container | The root container. |
register() | Sync. Register container bindings only. |
boot() | Async. Startup wiring after all providers register. |
shutdown() | Async. Teardown, reverse order. |
commands() | Return CLI command classes/instances this provider contributes. |
publishes(paths, *, tag=, is_migrations=) | Declare files an app can copy out with vendor:publish. |
safe_config(cls, *, default) | Resolve a config object, falling back to default if it isn't available. |
See Request Lifecycle for the full register-vs-boot ordering.
Registering Providers¶
All service providers are registered in the bootstrap/providers.py file. This file exposes a providers list:
from arvel.providers import ServiceProvider
from app.providers.app_service_provider import AppServiceProvider
providers: list[type[ServiceProvider]] = [
AppServiceProvider,
]
Your providers run after the framework's baseline providers, so framework services are already bound by the time your register() runs.
Contributing CLI Commands¶
A provider exposes console commands by returning them from commands(). The framework collects them from every provider when the app boots:
class AppServiceProvider(ServiceProvider):
def commands(self) -> list[type]:
return [SyncInventoryCommand]
You may return command classes (instantiated with no arguments) or pre-built command instances (useful when a command needs injected dependencies). See CLI Commands.
Publishing Assets¶
Package authors declare files an application can copy into its own tree. Call publishes from boot, mapping source paths to destinations:
from pathlib import Path
async def boot(self) -> None:
package_root = Path(__file__).parent
self.publishes(
{package_root / "migrations": "database/migrations"},
tag="my-package",
is_migrations=True,
)
There's no self.package_path — resolve your package directory yourself (here, from the provider module's own __file__).
Applications then copy them out:
Framework Providers Reference¶
Auto-Registered Providers¶
These baseline providers register automatically — you don't list them, but knowing they exist explains where framework bindings come from:
| Provider | Binds / does |
|---|---|
ConfigServiceProvider | Config accessor; one singleton per registered ArvelSettings. |
LogServiceProvider | No-op placeholder; the OpenTelemetry-backed Log facade is bootstrapped by ObservabilityServiceProvider. |
LangServiceProvider | Translator for localization. |
ContextServiceProvider | Per-request context store (the Context facade). |
ObservabilityServiceProvider | Tracing, logging, metrics. |
DatabaseServiceProvider | Async engine, session maker, AsyncSession, Schema. |
HttpServiceProvider | Router, exception handler, rate-limiter store, maintenance manager. |
SchedulerServiceProvider | Schedule, scheduler kernel; discovers app/console/kernel.py. |
ConsoleServiceProvider | Collects every provider's commands() (always registered last). |
Opt-In Providers¶
Many subsystems are not auto-registered. Add their provider to bootstrap/providers.py to use them, or their facade raises FacadeNotBoundError (or a RuntimeError):
| Provider | Import | Enables |
|---|---|---|
CacheServiceProvider | arvel.providers.cache_provider | Cache |
SessionServiceProvider | arvel.providers.session_provider | Session |
StorageServiceProvider | arvel.providers.storage_provider | Storage |
BroadcastServiceProvider | arvel.providers.broadcast_provider | Broadcast |
AuthServiceProvider | arvel.auth.provider | Auth, Gate, auth routes |
MailServiceProvider | arvel.mail.providers.mail_service_provider | Mail |
QueueServiceProvider | arvel.queue.providers.queue_service_provider | Bus, queue commands |
EventServiceProvider | arvel.events.providers.event_service_provider | Event |
NotificationServiceProvider | arvel.notifications.providers.notification_service_provider | Notification |
from arvel.providers.cache_provider import CacheServiceProvider
from arvel.providers.storage_provider import StorageServiceProvider
providers: list[type[ServiceProvider]] = [
CacheServiceProvider,
StorageServiceProvider,
AppServiceProvider,
]
Note
Companion packages (arvel-oauth, arvel-permission, …) are not auto-registered either. Add their provider to bootstrap/providers.py yourself. See the packages overview.
Provider Ordering¶
The framework resolves the provider chain in a fixed order: the baseline head providers (config, logging, language, context, observability, database, HTTP, scheduler) run first, then your providers in the order you list them, and finally the ConsoleServiceProvider is forced last so it can collect commands from everything else. Duplicate entries are de-duplicated, so re-listing a baseline provider is a harmless no-op.