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()