Wireup¶
Type-driven dependency injection for Python. Wireup is battle-tested in production, thread-safe, no-GIL (PEP 703) ready, and designed to fail fast: if the container starts, it works.
Inject a dense dependency graph in FastAPI + Uvicorn on every request
(Requests per second, higher is better. Manual Wiring represents the upper bound.)
-
Correct by Default
Wireup catches missing dependencies, circular references, lifetime mismatches, duplicate registrations, and missing config keys at startup. Shared dependencies are created in a thread-safe way.
-
Define Once, Inject Anywhere
Reuse the same service layer across APIs, CLIs, workers, and scripts without rewriting your dependency wiring.
-
Framework-Ready
Native integrations for FastAPI, Flask, Django, Starlette, AIOHTTP, ASGI, FastMCP, Celery, Click, Typer, and Strawberry.
-
Startup-Resolved Injection
Resolve constructor dependencies at startup in FastAPI and AIOHTTP class-based handlers, not per request.
Quick Start¶
pip install wireup
Framework-first setup with request-time injection in endpoints.
import fastapi
import wireup
import wireup.integration.fastapi
from wireup import Injected, injectable
@injectable
class Database:
def query(self, sql: str) -> list[str]: ...
@injectable
class UserService:
def __init__(self, db: Database) -> None:
self.db = db
def get_users(self) -> list[str]:
return self.db.query("SELECT name FROM users")
app = fastapi.FastAPI()
@app.get("/users")
def get_users(service: Injected[UserService]) -> list[str]:
return service.get_users()
container = wireup.create_async_container(injectables=[Database, UserService])
wireup.integration.fastapi.setup(container, app)
Plain Python scripts with function injection and no framework dependency.
import wireup
from wireup import Injected, inject_from_container, injectable
@injectable
class Database:
def execute(self, sql: str) -> None: ...
container = wireup.create_sync_container(injectables=[Database])
@inject_from_container(container)
def run_migration(db: Injected[Database]) -> None:
db.execute("ALTER TABLE users ADD COLUMN active BOOLEAN DEFAULT true")
run_migration()
Inject configuration directly into constructors without writing pass-through factories.
from typing import Annotated
import wireup
from wireup import Inject, injectable
@injectable
class Database:
def __init__(self, db_url: Annotated[str, Inject(config="db_url")]) -> None:
self.db_url = db_url
def execute(self, sql: str) -> None: ...
container = wireup.create_sync_container(
injectables=[Database],
config={"db_url": "postgresql://localhost/app"},
)
Need strict boundaries? Use factories to wire pure domain objects and integrate external libraries like Pydantic. See Factories for the full pattern.
import wireup
from wireup import injectable
from domain import Database
from settings import Settings
@injectable
def make_settings() -> Settings:
return Settings()
@injectable
def make_database(settings: Settings) -> Database:
return Database(url=settings.db_url)
container = wireup.create_sync_container(
injectables=[make_settings, make_database]
)
Browse by Topic¶
-
New to Wireup?
Set up your first container, inject dependencies, and run your app.
-
Container Basics
Learn registration, retrieval, scopes, and overrides.
-
Injectables & Config
Define classes/functions and inject configuration.
-
Lifetimes & Resources
Model singleton/scoped/transient behavior and cleanup timing.
-
Advanced Composition
Use factories, interfaces, qualifiers, and reusable sub-graphs.
-
Testing & Overrides
Replace dependencies safely in tests without rewiring your app.
-
Framework Integrations
FastAPI, Flask, Django, AIOHTTP, Starlette, ASGI, FastMCP, Celery, Strawberry, Click, and Typer.
-
Function Injection
Inject into scripts, jobs, handlers, and framework callbacks.
Core Concepts at a Glance¶
| Concept | What it gives you | Deep dive |
|---|---|---|
| Container | Centralized dependency graph and lifecycle management | Container |
| Injectables | Type-based dependency wiring for classes and functions | Injectables |
| Configuration | Config injection with startup validation | Configuration |
| Lifetimes | singleton, scoped, transient instance control |
Lifetimes & Scopes |
| Factories | Advanced creation patterns for complex dependencies | Factories |
| Resources | Initialization and cleanup patterns | Resources |
| Overrides | Swap dependencies for tests and local experimentation | Testing |
Validation checks¶
Wireup validates dependencies and configuration at startup.
@injectable
class Foo:
def __init__(self, oops: UnknownDep) -> None: ...
container = wireup.create_sync_container(injectables=[Foo])
# ❌ Parameter 'oops' of 'Foo' depends on an unknown injectable 'UnknownDep'.
container = wireup.create_sync_container(injectables=[])
@inject_from_container(container)
def my_function(oops: Injected[UnknownDep]) -> None: ...
# ❌ Parameter 'oops' of 'my_function' depends on an unknown injectable 'UnknownDep'.
@injectable
class Database:
def __init__(
self, url: Annotated[str, Inject(config="db_url")]
) -> None: ...
container = wireup.create_sync_container(injectables=[Database], config={})
# ❌ Parameter 'url' of Type 'Database' depends on an unknown Wireup config key 'db_url'.
Additional checks include circular dependencies, lifetime mismatches, and duplicate registrations. See Container, Configuration, and Function Injection for details.
Next Steps¶
- Read Getting Started for an end-to-end setup.
- Pick your framework from Integrations.
- Review Testing before writing integration tests.