Recipes

Keep container calls at application boundaries.

These recipes show where Injex belongs in common Python application shapes. The examples are small because the boundary is the important part: build the graph at startup, resolve use cases at entrypoints, and keep domain code unaware of the container.

FastAPI composition root

Build the container during application startup. FastAPI dependencies can create a scope and resolve use cases for handlers.

from fastapi import Depends, FastAPI
from injex import Container, Scope

container = Container()
container.add_scoped(UserRepository)
container.add_transient(RegisterUser)
container.assert_valid()

app = FastAPI()

def get_scope() -> Scope:
    return container.create_scope()

def get_register_user(scope: Scope = Depends(get_scope)) -> RegisterUser:
    return scope.resolve(RegisterUser)

@app.post("/users")
def create_user(use_case: RegisterUser = Depends(get_register_user)):
    return use_case.execute()

Register request-owned objects as scoped services. Keep long-lived clients as singletons.

Worker job scope

Keep process-level clients outside the job loop, then create a job container and one scope per message.

queue_client = QueueClient()

def build_job_container(job_id: str) -> Container:
    container = Container()
    container.add_instance(QueueClient, queue_client)
    container.add_instance(JobContext, JobContext(job_id))
    container.add_scoped(JobScratchpad)
    container.add_transient(ImportUserJob)
    container.assert_valid()
    return container

def handle_job(job_id: str) -> None:
    scope = build_job_container(job_id).create_scope()
    scope.resolve(ImportUserJob).run()

Do not reuse scoped state between jobs. For concurrent jobs, keep job-specific values in the per-job container or pass them as method arguments.

CLI command wiring

Let the CLI parse arguments, build settings, resolve a command object, and run it. Keep clients and services out of module-level globals.

def build_container(settings: Settings) -> Container:
    container = Container()
    container.add_instance(Settings, settings)
    container.add_singleton(ApiClient)
    container.add_transient(SyncUsersCommand)
    container.assert_valid()
    return container

def main() -> None:
    settings = Settings(api_url="https://api.example.com")
    container = build_container(settings)
    container.resolve(SyncUsersCommand).run()

Test override boundary

Replace dependencies around the smallest block that needs the fake. The original registration is restored when the block exits.

fake_client = FakeApiClient()

with container.override(ApiClient, instance=fake_client):
    command = container.resolve(SyncUsersCommand)
    command.run()