Skip to main content

philiprehberger-di

Tests PyPI version Last updated

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:

⭐ Star the repo

🐛 Report issues

💡 Suggest features

❤️ Sponsor development

🌐 All Open Source Projects

💻 GitHub Profile

🔗 LinkedIn Profile

License

MIT

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)

Source distribution for philiprehberger-di 0.6.0
File Size Uploaded
philiprehberger_di-0.6.0.tar.gz 183.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for philiprehberger-di 0.6.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page