https://github.com/maldoinc/wireup
Performant, concise, and easy-to-use dependency injection container for Python 3.8+.
https://github.com/maldoinc/wireup
aiohttp dependency-injection dependency-injection-container dependency-injection-framework dependency-injector design-pattern di django fastapi flask ioc-container python service-locator
Last synced: 12 days ago
JSON representation
Performant, concise, and easy-to-use dependency injection container for Python 3.8+.
- Host: GitHub
- URL: https://github.com/maldoinc/wireup
- Owner: maldoinc
- License: mit
- Created: 2023-08-19T22:09:32.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2026-02-21T13:08:22.000Z (21 days ago)
- Last Synced: 2026-02-21T17:56:19.629Z (21 days ago)
- Topics: aiohttp, dependency-injection, dependency-injection-container, dependency-injection-framework, dependency-injector, design-pattern, di, django, fastapi, flask, ioc-container, python, service-locator
- Language: Python
- Homepage: https://maldoinc.github.io/wireup/
- Size: 7.33 MB
- Stars: 354
- Watchers: 4
- Forks: 20
- Open Issues: 15
-
Metadata Files:
- Readme: readme.md
- Contributing: .github/contributing.md
- License: license.md
Awesome Lists containing this project
- awesome-dependency-injection-in-python - Wireup - Concise, Powerful, and Type-Safe Python Dependency Injection Library. [๐, MIT License]. (Software / DI Frameworks / Containers)
- awesome-django - Wireup - Dependency Injection for Django (Third-Party Packages / Dependency Injection)
- awesome-fastapi - Wireup - Inject dependencies with zero runtime overhead in FastAPI; Share dependencies across web, cli or other interfaces. (Third-Party Extensions / Dependency Injection)
README
Wireup
Type-driven dependency injection for Python. Wireup is battle-tested in production, thread-safe, no-GIL (PEP 703) ready, and fail-fast by design: if the container starts, it works.
[](https://github.com/maldoinc/wireup)
[](https://github.com/maldoinc/wireup)
[](https://pypi.org/project/wireup/)
[](https://pypi.org/project/wireup/)
[](https://maldoinc.github.io/wireup)
[](https://maldoinc.github.io/wireup)
Inject a dense dependency graph in FastAPI + Uvicorn on every request
(Requests per second, higher is better. Manual Wiring represents the upper bound.).
Full methodology and reproducibility: benchmarks.
> [!TIP]
> **New**: Inject singleton dependencies in FastAPI with zero overhead using [Class-Based Handlers](https://maldoinc.github.io/wireup/latest/integrations/fastapi/class_based_handlers/).
## Why Wireup?
- **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 in APIs, CLIs, workers, and scripts.
- **Framework-ready**: native integrations for **FastAPI**, **Flask**, **Django**, **Starlette**, **AIOHTTP**, **ASGI**, **FastMCP**, **Celery**, **Click**, **Typer**, and **Strawberry**. [See Integrations](https://maldoinc.github.io/wireup/latest/integrations).
- **Startup-resolved constructor injection for [FastAPI](https://maldoinc.github.io/wireup/latest/integrations/fastapi/class_based_handlers/) and [AIOHTTP](https://maldoinc.github.io/wireup/latest/integrations/aiohttp/class_based_handlers/) handlers**: constructor dependencies are resolved once at startup, not per request. [FastAPI class-based handlers](https://maldoinc.github.io/wireup/latest/integrations/fastapi/class_based_handlers/).
- **Test overrides with context managers**: replace any injectable for a test scope and restore automatically. [See testing docs](https://maldoinc.github.io/wireup/latest/testing/).
- **Reusable sub-graphs**: run multiple configured instances of the same dependency graph without spinning up separate containers. [See reusable factory bundles](https://maldoinc.github.io/wireup/latest/factories/#reusable-factory-bundles).
## Installation
```bash
pip install wireup
```
## Complete Example
```python
@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)
```
For a fully working end-to-end walkthrough, see the [Getting Started guide](https://maldoinc.github.io/wireup/latest/getting_started/).
## Features
### โก Clean & Type-Safe DI
Use decorators and annotations for concise, co-located definitions, or factories to keep your domain model pure and decoupled.
**1. Basic Usage**
Register classes with `@injectable` and let the container resolve dependencies automatically.
```python
@injectable
class Database:
def __init__(self) -> None:
self.engine = sqlalchemy.create_engine("sqlite://")
@injectable
class UserService:
def __init__(self, db: Database) -> None:
self.db = db
container = wireup.create_sync_container(injectables=[Database, UserService])
# Inject via framework integration or @inject_from_container (recommended)
@app.get("/users")
def get_users(service: Injected[UserService]) -> list[str]: ...
# Or resolve directly for advanced use cases (middleware, startup, scripts)
user_service = container.get(UserService)
```
**2. Inject Configuration**
Inject configuration alongside dependencies. No need to write factories just to pass a config value.
View Code
```python
@injectable
class Database:
def __init__(self, url: Annotated[str, Inject(config="db_url")]) -> None:
self.engine = sqlalchemy.create_engine(url)
container = wireup.create_sync_container(
injectables=[Database],
config={"db_url": os.environ["DB_URL"]}
)
```
**3. Clean Architecture**
Need strict boundaries? Use factories to wire pure domain objects and integrate external libraries like Pydantic.
```python
# 1. No Wireup imports
class Database:
def __init__(self, url: str) -> None:
self.engine = create_engine(url)
# 2. Configuration (Pydantic)
class Settings(BaseModel):
db_url: str = "sqlite://"
```
```python
# 3. Wireup factories
@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])
```
**4. Auto-Discover**
No need to list every injectable manually. Scan entire modules or packages to register all at once. This is the recommended default for larger applications.
View Code
```python
import app
import wireup
container = wireup.create_sync_container(
injectables=[
app.services,
app.repositories,
app.factories
]
)
```
### ๐ฏ Function Injection
Inject dependencies into any function. CLI commands, background tasks, event handlers, or any standalone function that needs container access.
```python
@inject_from_container(container)
def migrate_database(db: Injected[Database], settings: Injected[Settings]) -> None:
...
```
### ๐ Interfaces & Abstractions
Depend on abstractions, not implementations. Bind implementations to interfaces using Protocols or ABCs.
```python
class Notifier(Protocol):
def notify(self) -> None: ...
@injectable(as_type=Notifier)
class SlackNotifier:
def notify(self) -> None: ...
container = create_sync_container(injectables=[SlackNotifier])
# SlackNotifier is injected wherever Notifier is requested
@app.post("/notify")
def send_notification(notifier: Injected[Notifier]) -> None:
notifier.notify()
```
### ๐ญ Flexible Creation Patterns
Defer instantiation to factories when initialization or cleanup is non-trivial. Full support for sync, async, and generator factories. Wireup handles cleanup at the right time based on lifetime.
```python
class WeatherClient:
def __init__(self, client: requests.Session) -> None:
self.client = client
@injectable
def weather_client_factory() -> Iterator[WeatherClient]:
with requests.Session() as session:
yield WeatherClient(client=session)
```
Async example
```python
class WeatherClient:
def __init__(self, client: aiohttp.ClientSession) -> None:
self.client = client
@injectable
async def weather_client_factory() -> AsyncIterator[WeatherClient]:
async with aiohttp.ClientSession() as session:
yield WeatherClient(client=session)
```
### ๐ Managed Lifetimes
Declare dependencies as singleton, scoped, or transient to control instance reuse.
```python
# Singleton: one instance per application (default)
@injectable
class Settings:
pass
# Async singleton with cleanup โ no lru_cache, no app.state
@injectable
async def database_factory(settings: Settings) -> AsyncIterator[AsyncConnection]:
async with create_async_engine(settings.db_url).connect() as connection:
yield connection
# Scoped: one instance per request, shared within that request
@injectable(lifetime="scoped")
class RequestContext:
def __init__(self) -> None:
self.request_id = uuid4()
# Transient: fresh instance every time
@injectable(lifetime="transient")
class OrderProcessor:
pass
```
### โ Optional Dependencies
First-class support for `Optional[T]` and `T | None`.
```python
@injectable
def make_cache(settings: Settings) -> RedisCache | None:
return RedisCache(settings.redis_url) if settings.cache_enabled else None
@injectable
class UserService:
def __init__(self, cache: RedisCache | None) -> None:
self.cache = cache
# Retrieve optional dependencies directly when needed
cache = container.get(RedisCache | None)
```
### ๐งฉ Reusable sub-graphs
Need to register multiple sub-graphs with different settings (e.g. primary + analytics DB)?
Wireup supports this natively without requiring a dedicated provider class or a separate container.
See [Reusable Factory Bundles](https://maldoinc.github.io/wireup/latest/factories/#reusable-factory-bundles).
### ๐ก๏ธ Startup Validation
Wireup validates the entire dependency graph when the container is created.
```python
# Missing dependencies: caught at startup, not at runtime
@injectable
class Foo:
def __init__(self, unknown: NotManagedByWireup) -> None: ...
container = wireup.create_sync_container(injectables=[Foo])
# โ Parameter 'unknown' of 'Foo' depends on an unknown injectable 'NotManagedByWireup'.
```
```python
container = wireup.create_sync_container(injectables=[])
# Decorated functions validated at import time
@inject_from_container(container)
def my_function(oops: Injected[NotManagedByWireup]): ...
# โ Parameter 'oops' of 'my_function' depends on an unknown injectable 'NotManagedByWireup'.
```
```python
# Missing config keys caught at startup
@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: circular dependencies, lifetime mismatches (e.g. singleton depending on scoped), and duplicate registrations.
### ๐ Framework Independent
Define your service layer once. Run it anywhere.
```python
# Define once
# injectables = [UserService, Database, ...]
# FastAPI (native integration, no extra decorator needed)
@app.get("/users")
async def view(service: Injected[UserService]): ...
# Click
@click.command()
def command(service: Injected[UserService]): ...
# Use @inject_from_container to inject dependencies into any function.
# Most useful for scripts or when no Wireup integration is available.
@inject_from_container(container)
def run_worker(service: Injected[UserService]): ...
```
Have a useful integration to recommend? Create an issue or PR!
### ๐ Framework Integrations
Native integrations manage request scopes, endpoint injection, and dependency lifetimes.
Supported: **FastAPI**, **Flask**, **Django**, **AIOHTTP**, **Starlette**, **Click**, **Typer**, **Strawberry**
[View all integrations โ](https://maldoinc.github.io/wireup/latest/integrations/)
### ๐งช Simplified Testing
Wireup decorators only collect metadata. Injectables are plain classes and functions. Test them directly with no special setup.
Swap dependencies during tests with `container.override`:
```python
with container.override.injectable(target=Database, new=in_memory_database):
# All code that depends on Database will receive in_memory_database
# for the duration of this context manager
response = client.get("/users")
```
## ๐ Documentation
[https://maldoinc.github.io/wireup](https://maldoinc.github.io/wireup)
If Wireup helps your team move faster, consider starring the repo to help more Python developers discover it.