Lightweight dependency injection container for Python.
pip install philiprehberger-diLightweight dependency injection container for Python.
pip install philiprehberger-di
from philiprehberger_di import Container
container = Container()
container.register(Logger)
logger = container.resolve(Logger)
container.register(Database, singleton=True)
a = container.resolve(Database)
b = container.resolve(Database)
assert a is b
container.register(Cache, factory=lambda: Cache(max_size=256))
cache = container.resolve(Cache)
class Service:
def __init__(self, db: Database, logger: Logger) -> None:
self.db = db
self.logger = logger
container.register(Database)
container.register(Logger)
container.register(Service)
service = container.resolve(Service) # db and logger are injected automatically
from philiprehberger_di import Container, inject
container = Container()
container.register(Logger, singleton=True)
@inject(container)
def handle_request(logger: Logger) -> str:
logger.log("request handled")
return "ok"
handle_request() # logger is resolved and injected automatically
container.register(
Database,
singleton=True,
on_create=lambda db: db.connect(),
on_destroy=lambda db: db.disconnect(),
)
db = container.resolve(Database) # on_create called after creation
container.reset() # on_destroy called before clearing singletons
The container detects circular dependencies during resolution and raises a
CircularDependencyError with the full dependency chain:
from philiprehberger_di import CircularDependencyError
class A:
def __init__(self, b: B) -> None: ...
class B:
def __init__(self, a: A) -> None: ...
container.register(A)
container.register(B)
try:
container.resolve(A)
except CircularDependencyError as e:
print(e) # Circular dependency detected: A -> B -> A
print(e.chain) # [A, B, A]
Services registered with Lifetime.SCOPED are singletons within a scope but
differ across scopes:
from philiprehberger_di import Container, Lifetime
container = Container()
container.register(RequestContext, lifetime=Lifetime.SCOPED)
container.register(Logger, lifetime=Lifetime.SINGLETON)
with container.create_scope() as scope:
ctx1 = scope.resolve(RequestContext)
ctx2 = scope.resolve(RequestContext)
assert ctx1 is ctx2 # same within the scope
# on_destroy hooks are called when the scope exits
Defer construction of an expensive service until first use:
from philiprehberger_di import Container, Lifetime
container = Container()
container.register(ExpensiveClient, lifetime=Lifetime.SINGLETON)
proxy = container.lazy(ExpensiveClient)
# ExpensiveClient is NOT yet constructed
result = proxy.do_thing() # constructed on first access, cached afterwards
Check whether a type is registered, or remove it. Useful when wiring up tests:
container.is_registered(Database) # True / False
container.register(Database, singleton=True)
container.unregister(Database) # also clears cached singleton + calls on_destroy
| Function / Class | Description |
|---|---|
Container() | Create a new dependency injection container |
container.register(cls, factory?, singleton?, lifetime?, on_create?, on_destroy?) | Register a class with optional factory, lifetime, and lifecycle hooks |
container.unregister(cls) | Remove a registration; clears any cached singleton and calls on_destroy |
container.is_registered(cls) | Return whether cls is registered in this container |
container.resolve(cls) | Resolve an instance, recursively injecting dependencies |
container.lazy(cls) | Return a Lazy[cls] proxy that resolves on first use |
container.create_scope() | Create a child scope for scoped lifetime management |
container.reset() | Call on_destroy for singletons with hooks, then clear the cache |
inject(container) | Decorator that resolves type-hinted params from the container |
Lifetime.TRANSIENT | New instance on every resolve (default) |
Lifetime.SINGLETON | Single shared instance across the container |
Lifetime.SCOPED | Single instance per scope, transient without a scope |
CircularDependencyError | Raised when a circular dependency chain is detected |
Scope | Child scope returned by create_scope(), used as a context manager |
Lazy[T] | Proxy that resolves the underlying service on first access |
pip install -e .
python -m pytest tests/ -v
If you find this project useful: