Application Bootstrapping
Bootstrapping is the Orchestration Layer of Framework M. It is the definitive way to initialize database engines, bind DI containers, and configure services without creating hard-coded imports or "magic" startup logic.
The Core Concept
The framework uses a Deferred Discovery pattern:
- Registry: Packages register their own startup steps via entry points.
- Discovery: On startup, the framework finds all registered steps across all installed packages.
- Orchestration: The framework sorts the steps by their
orderand runs them sequentially.
Implementing a Bootstrap Step
To create a startup step, implement the BootstrapProtocol from framework_m_core.interfaces.bootstrap.
# my_app/bootstrap.py
from typing import Any
from framework_m_core.interfaces.bootstrap import BootstrapProtocol
class AppInitialization(BootstrapProtocol):
"""Initializes the application-specific DI container."""
name: str = "init_app_di"
order: int = 40 # Standard order for DI registration
def run(self, container: Any) -> None:
"""Hydration phase (Sync): Wire the application 'brain'."""
# 1. Standard Metadata Decoration
from framework_m_core.metadata_decorator import MetadataDecoratorRegistry
registry = MetadataDecoratorRegistry.get_instance()
registry.register("MyDocType", my_decorator_fn)
# 2. Activation phase (Async): Register pipes to be opened in Lifespan
async def activate_service(c: Any):
# This runs during the app lifespan (Activation)
await c.my_service().connect()
container.startup_hooks.add_args(activate_service)
print("✓ App Hydrated and Activation Hook Registered")
Hydration vs. Activation
Framework M uses a strictly separated two-phase initialization model to ensure deterministic startup across all contexts (CLI, API, REPL).
- Hydration (Synchronous): Happens inside
run(). Use this for wiring, metadata registration, and DI overrides. No event loop is required. - Activation (Asynchronous): Happens during the application lifespan. Use
container.startup_hooksto register functions that requireawait(e.g., connecting to a message bus).
Registering Async Hooks
If your service needs to establish a connection, register an async hook during the sync bootstrap:
def run(self, container: Any) -> None:
async def connect_to_nats(c: Any):
await c.event_bus().connect()
async def disconnect_from_nats(c: Any):
await c.event_bus().disconnect()
container.startup_hooks.add_args(connect_to_nats)
container.shutdown_hooks.add_args(disconnect_from_nats)
Standard Execution Orders
| Order | Responsibility | Framework Action (Automatic) |
|---|---|---|
| 10 | Storage Engines | Initialize DB/Secrets |
| 20 | Registries | Hydration Phase: Metadata/Field decoration |
| 30 | Schemas | Migration Engine readiness |
| 40 | DI / Overrides | DI Phase: App Containers & Overrides loaded |
| 50+ | App Logic | Cache warm-up, business initialization |
[!NOTE] The framework automatically executes
load_overrides()andauto_load_app_containers()before running yourorder=40bootstrap tasks. You only need to implement custom overrides or logic.
Registration
Register your step in your package's pyproject.toml. This is how the framework "discovers" your step.
[project.entry-points."framework_m.bootstrap"]
init_app = "my_app.bootstrap:AppInitialization"
[!TIP] Why use this instead of
main.py? By using Bootstrap steps, you allow your library to be "plug-and-play." Any application that installs your package will automatically run your initialization code without the developer needing to add manual imports to their application logic.Refer to the Dependency Injection Guide for details on loading containers and overriding services during bootstrap.