Skip to content

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.

Scoped Performance Scoped Performance

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.

    Validation checks

  • Define Once, Inject Anywhere


    Reuse the same service layer across APIs, CLIs, workers, and scripts without rewriting your dependency wiring.

    Function injection

  • Framework-Ready


    Native integrations for FastAPI, Flask, Django, Starlette, AIOHTTP, ASGI, FastMCP, Celery, Click, Typer, and Strawberry.

    View integrations

  • Startup-Resolved Injection


    Resolve constructor dependencies at startup in FastAPI and AIOHTTP class-based handlers, not per request.

    FastAPI class-based handlers

Quick Start

pip install wireup

Framework-first setup with request-time injection in endpoints.

main.py
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.

script.py
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.

config_example.py
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.

wiring.py
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.

    Getting Started

  • Container Basics

    Learn registration, retrieval, scopes, and overrides.

    Container

  • Injectables & Config

    Define classes/functions and inject configuration.

    InjectablesConfiguration

  • Lifetimes & Resources

    Model singleton/scoped/transient behavior and cleanup timing.

    Lifetimes & ScopesResource Management

  • Advanced Composition

    Use factories, interfaces, qualifiers, and reusable sub-graphs.

    FactoriesInterfaces

  • Testing & Overrides

    Replace dependencies safely in tests without rewiring your app.

    Testing

  • Framework Integrations

    FastAPI, Flask, Django, AIOHTTP, Starlette, ASGI, FastMCP, Celery, Strawberry, Click, and Typer.

    Integrations

  • Function Injection

    Inject into scripts, jobs, handlers, and framework callbacks.

    Function Injection

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

  1. Read Getting Started for an end-to-end setup.
  2. Pick your framework from Integrations.
  3. Review Testing before writing integration tests.