Boot Sequence
ProviderOrchestrator.boot_all() is the engine behind Application.start(). This page is the contract for writing providers. For states, health, and how to run an app, see Application Lifecycle.
Order of operations
Section titled “Order of operations”Application.__init__() └─ Container created; OrideconConfig + Application bound as singletons
add_module / add_provider (must happen while CREATED)
Application.start() ├─ validate config for the active environment ├─ discover_modules() if config.discovery.auto_discover ├─ ModuleCompiler.compile() if any modules are registered ├─ register() on every provider ← ContainerRegistrarProtocol ├─ container.freeze() ← singleton/transient/scoped now raise ├─ container.validate() ├─ boot() on every provider ← BootContainerProtocol └─ RUNNINGregister() and boot() of providers that share a dependency level run concurrently (asyncio.gather()). Levels themselves are serial and follow ProviderPriority plus the dependencies tuple.
add_module() / add_provider() only queue work. The register() call happens later, inside the orchestrator.
Phase 1 — register()
Section titled “Phase 1 — register()”Declarative only. Bind protocols to implementations. No I/O, no resolve().
from oridecon import Providerfrom oridecon.contracts.core import ProviderPriorityfrom oridecon.contracts.core.di import ContainerRegistrarProtocol
class DatabaseProvider(Provider): name = "database" priority = ProviderPriority.INFRASTRUCTURE
async def register(self, container: ContainerRegistrarProtocol) -> None: container.singleton(DatabaseProtocol, MySqlConnection) container.transient(UserRepository, UserRepositoryImpl)ContainerRegistrarProtocol has singleton(), transient(), scoped(), has(), and bind(). It does not have resolve(). mypy will flag await container.resolve(...) here.
If the provider declares config_key and config_model, the orchestrator injects provider.config from OrideconConfig.get_section() before register() runs.
Phase 2 — freeze + validate
Section titled “Phase 2 — freeze + validate”After every register() returns:
container.freeze()— furthersingleton()/transient()/scoped()raiseContainerError(ORI_ERR_DI_001).container.validate()— missing dependencies, cycles, scope violations.- Module export matching.
Any issue raises ContainerValidationError and boot aborts (nothing has connected yet).
Phase 3 — boot()
Section titled “Phase 3 — boot()”Resolve and initialize. This is where you connect, warm caches, wrap instances.
from oridecon.contracts.core.di import BootContainerProtocol
class DatabaseProvider(Provider): async def boot(self, container: BootContainerProtocol) -> None: db = await container.resolve(DatabaseProtocol) await db.connect()BootContainerProtocol is registrar plus resolver. After freeze, singleton() / transient() / scoped() still raise. To replace an already-registered singleton (wrap a client, inject a connected pool), use container.bind(service_type, instance):
async def boot(self, container: BootContainerProtocol) -> None: raw = await container.resolve(CacheBackend) await raw.connect() container.bind(CacheBackend, InstrumentedCache(raw))Providers boot in ascending priority:
| Priority | Value | Example |
|---|---|---|
CRITICAL | 0 | Configuration, diagnostics |
INFRASTRUCTURE | 10 | Database, cache, brokers |
SECURITY | 20 | Auth |
NORMAL | 30 | Default |
APPLICATION | 40 | CLI, admin |
DOMAIN | 50 | Business logic |
PRESENTATION | 80 | Web / API |
COMMS | 90 | Email, SMS, webhooks |
LOW | 100 | Optional, last |
If a required provider’s boot() fails, already-booted providers are shutdown() in reverse order (rollback) and the exception propagates. A provider with required = False logs a warning and the rest of boot continues.
boot_timeout (seconds) wraps boot() in asyncio.wait_for.
When every required provider has booted, on_module_booted() runs for each module. The app then becomes RUNNING. A banner is logged unless ORI_QUIET=1:
╔════════════════════════════════════════════════════════╗║ Oridecon 0.1.x ║║ Python 3.12.x ║║ ║║ Providers : 12 ║║ Modules : 4 ║╚════════════════════════════════════════════════════════╝Shutdown
Section titled “Shutdown”app.stop() (or leaving async with Application.boot(...)):
- State →
STOPPING on_module_shutdown()on every moduleon_before_shutdownlifecycle hooksshutdown()on providers in reverse boot order (parallel within a level)container.dispose()- State →
STOPPED
Every provider’s shutdown() is called even if one fails. Errors are collected; the first is re-raised after the rest finish. stop() is safe to call more than once.
class DatabaseProvider(Provider): def __init__(self) -> None: super().__init__() self._db: DatabaseProtocol | None = None
async def boot(self, container: BootContainerProtocol) -> None: self._db = await container.resolve(DatabaseProtocol) await self._db.connect()
async def shutdown(self) -> None: if self._db is not None: await self._db.disconnect()Complete provider
Section titled “Complete provider”from oridecon import Providerfrom oridecon.contracts.core import ProviderPriority, HealthCheckResult, HealthStatusfrom oridecon.contracts.core.di import ContainerRegistrarProtocol, BootContainerProtocol
class CacheProvider(Provider): name = "cache" priority = ProviderPriority.INFRASTRUCTURE dependencies = ("config",)
def __init__(self) -> None: super().__init__() self._backend: CacheBackend | None = None
async def register(self, container: ContainerRegistrarProtocol) -> None: container.singleton(CacheBackend, RedisCache)
async def boot(self, container: BootContainerProtocol) -> None: self._backend = await container.resolve(CacheBackend) await self._backend.connect()
async def shutdown(self) -> None: if self._backend is not None: await self._backend.disconnect()
async def health_check(self, timeout: float = 5.0) -> HealthCheckResult: return HealthCheckResult(component=self.name, status=HealthStatus.HEALTHY)Common mistakes
Section titled “Common mistakes”Resolve before start()
Section titled “Resolve before start()”The container exists from Application.__init__(), but it only has the app, config, and invoker until providers register. Resolving your services first raises UnresolvableDependencyError (ORI_ERR_DI_004).
app = Application()await app.container.resolve(DatabaseProtocol) # ORI_ERR_DI_004Resolve inside boot(), or after start() / inside async with Application.boot(...).
add_module / add_provider after boot
Section titled “add_module / add_provider after boot”app = Application()await app.start()app.add_module(ExtraModule) # RuntimeError: Cannot add_module after bootPass them to Application.boot(modules=..., providers=...) or call add_* while CREATED.
resolve() inside register()
Section titled “resolve() inside register()”ContainerRegistrarProtocol has no resolve(). Bind in register(), connect in boot().
Treating freeze as optional
Section titled “Treating freeze as optional”BootContainerProtocol structurally includes registrar methods, but the container is already frozen. singleton() during boot() raises ContainerError. Use bind() to replace a singleton that you registered in phase 1.
Next steps
Section titled “Next steps”- Application Lifecycle — states, health, ASGI,
run_application - Providers — priorities, config injection, discovery
- Container Protocols — why
registerandboottake different types