philiprehberger-di
Lightweight dependency injection container for Python.
Installation
pip install philiprehberger-di
Usage
from philiprehberger_di import Container
container = Container()
container.register(Logger)
logger = container.resolve(Logger)
Singletons
container.register(Database, singleton=True)
a = container.resolve(Database)
b = container.resolve(Database)
assert a is b
Custom Factories
container.register(Cache, factory=lambda: Cache(max_size=256))
cache = container.resolve(Cache)
Recursive Resolution
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
Inject Decorator
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
Lifecycle Hooks
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
Circular Dependency Detection
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]
Scoped Lifetime
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
Lazy Resolution
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
Introspection
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
Introspection and full clear
List every type registered in the container, or wipe it clean:
container.register(Logger)
container.register(Database, singleton=True)
container.registered_types() # [Logger, Database] — snapshot, in insertion order
container.clear() # removes ALL registrations and cached singletons
# (calls on_destroy first for any cached singletons)
Unlike reset(), which only clears the singleton cache and keeps
registrations intact, clear() empties the container entirely.
API
| 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 |
container.registered_types() |
Return a snapshot list of all registered types (insertion order) |
container.clear() |
Remove all registrations and cached singletons (calls on_destroy first) |
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 |
Development
pip install -e .
python -m pytest tests/ -v
Support
If you find this project useful:
License
Release files for philiprehberger-di 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| philiprehberger_di-0.6.0.tar.gz | 183.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| philiprehberger_di-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 191.4 kB
Release files / philiprehberger_di-0.6.0.tar.gz
| Download URL | philiprehberger_di-0.6.0.tar.gz |
|---|---|
| Size | 183.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
312579559536624ce861bae4ea6306d1d97feda9a68804fbb25b0bb414c52c13
|
|
BLAKE2b-256 checksum How to use checksums |
73bb625da513a4e33bdba3332cdd33eef5325616745bb9cfa02d7e4b47f7b367
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / philiprehberger_di-0.6.0-py3-none-any.whl
| Download URL | philiprehberger_di-0.6.0-py3-none-any.whl |
|---|---|
| Size | 8.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d7f92b7d18af402f0307cae421f46ecf5d70859b3c2b98184ea2e4611d92a051
|
|
BLAKE2b-256 checksum How to use checksums |
abf9b809f6a90b511b48277add19a70adee793721126ac5b3f37b0b628cac811
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|