Skip to main content

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:

  1. Registry: Packages register their own startup steps via entry points.
  2. Discovery: On startup, the framework finds all registered steps across all installed packages.
  3. Orchestration: The framework sorts the steps by their order and 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).

  1. Hydration (Synchronous): Happens inside run(). Use this for wiring, metadata registration, and DI overrides. No event loop is required.
  2. Activation (Asynchronous): Happens during the application lifespan. Use container.startup_hooks to register functions that require await (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

OrderResponsibilityFramework Action (Automatic)
10Storage EnginesInitialize DB/Secrets
20RegistriesHydration Phase: Metadata/Field decoration
30SchemasMigration Engine readiness
40DI / OverridesDI Phase: App Containers & Overrides loaded
50+App LogicCache warm-up, business initialization

[!NOTE] The framework automatically executes load_overrides() and auto_load_app_containers() before running your order=40 bootstrap 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.