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
Metadata
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 | Provenance | Actions |
|---|---|---|---|---|
| philiprehberger_di-0.6.0.tar.gz | 183.3 kB | Without provenance | Details for philiprehberger_di-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| philiprehberger_di-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for philiprehberger_di-0.6.0-py3-none-any.whl |
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
|
Security for philiprehberger-di 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.