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: 7 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 (about 2 months ago)
- Last Synced: 2026-02-21T17:56:19.629Z (about 2 months 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/)
Quick Start ยท
Docs ยท
Benchmarks ยท
Migrate to Wireup
## Why Wireup?
๐ Define Once, Use Anywhere
Reuse the same application code in APIs, CLIs, workers, and scripts without rewriting your wiring.
โ
Correct by Default
If the container starts, your dependency graph is valid. Wireup checks for missing or misconfigured dependencies to avoid surprises at runtime.
๐ Framework-Ready
Native integrations for FastAPI, Django, Flask, Starlette, Celery, Click, Typer, and more.
โก Zero-Overhead Handlers
Resolve singleton constructor dependencies once at startup in FastAPI and AIOHTTP class-based handlers, not per request.
๐งฉ Advanced Wiring
Go beyond simple constructor injection with
reusable bundles,
explicit scope context sharing, and
more using plain Python.
๐งช Easy to test
Override dependencies with context managers, keep tests isolated, and restore the original graph automatically.
## Benchmarks
Dense dependency graph resolved per request in FastAPI + Uvicorn
(Requests per second, higher is better. Manual Wiring represents the upper bound.)
Full methodology and reproducibility: benchmarks.
## Installation
```bash
pip install wireup
```
## Quick Start
```python
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)
```
For a full end-to-end walkthrough, start with the [Getting Started guide](https://maldoinc.github.io/wireup/latest/getting_started/).
## Basic Usage
Wireup also supports config injection, decorator-free domain models, and package-level registration.
**1. Inject Configuration**
Inject configuration alongside dependencies. No need to write factories just to pass a config value.
```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"]}
)
```
**2. 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])
```
**3. Package-level registration**
No need to list every injectable manually. Provide entire modules or packages to register all at once.
```python
import app
import wireup
container = wireup.create_sync_container(
injectables=[
app.services,
app.repositories,
app.factories
]
)
```
## More Features
### ๐ฏ Function Injection
Inject dependencies into 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
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: ...
# SlackNotifier is injected wherever Notifier is requested
@app.post("/notify")
def send_notification(notifier: Injected[Notifier]) -> None:
notifier.notify()
```
### ๐ญ Factories & Resources
Defer instantiation to specialized factories when complex initialization or cleanup is required.
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)
```
### ๐ Lifetimes & Scopes
Declare dependencies as `singleton`, `scoped`, or `transient` to control reuse explicitly.
```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
```
### ๐ก๏ธ Startup Validation
Wireup validates the 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'.
```
It also catches circular dependencies, duplicate registrations, misconfigured lifetimes, and missing config at startup.
### ๐งช Testing
Wireup decorators only collect metadata. Injectables are plain classes and functions, so you can 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):
# Injectables that depend on Database will receive in_memory_database
# for the duration of this context manager
response = client.get("/users")
```
## ๐ Documentation
See the docs for integrations, lifetimes, factories, testing, and more advanced patterns.
[https://maldoinc.github.io/wireup](https://maldoinc.github.io/wireup)
If Wireup is useful to you, a star on GitHub helps others find it.