A lightweight, type-safe dependency injection framework for Python that supports singleton, lazy singleton, and factory registrations, with scoped overrides and flexible parameter injection.
- Registry-based DI: Register services and resolve them by type and optional tags.
- Scoped overrides: Create temporary overrides using context managers.
- Injection decorator: Automatically inject dependencies into functions, methods, or constructors.
- Multiple registration modes:
SINGLETON: Single instance shared globally.LAZY_SINGLETON: Created on first access and then cached.FACTORY: New instance created on every resolution.
- Tag-based resolution: Support multiple implementations of the same interface.
- Type-safe: Uses Python type annotations for automatic injection.
- Supports instance, class, and static methods.
The package provides a pre-created global locator available from the module:
from di import locator, inject, RegistrationMode, NotRegisteredErrorYou can register services directly in the global locator:
from di import locator, RegistrationMode
class Service:
pass
locator.register(Service, lambda: Service(), RegistrationMode.SINGLETON)
locator.register(int, lambda: 42, RegistrationMode.SINGLETON, tag="answer")Resolve dependencies manually:
service_instance = locator.resolve(Service)
number = locator.resolve(int, tag="answer")from di import locator, inject
@inject(locator, params=["service", "num:answer"])
def process(service: Service, num: int):
return service, num
s, n = process()Supports injection into:
-
Instance methods:
class MyClass: @inject(locator, params=["service"]) def method(self, service: Service): return service
-
Class methods:
class MyClass: @classmethod @inject(locator, params=["service"]) def method(cls, service: Service): return service, cls
-
Static methods:
class MyClass: @staticmethod @inject(locator, params=["service"]) def method(service: Service): return service
-
Constructors:
class MyClass: @inject(locator, params=["service"]) def __init__(self, service: Service): self.service = service
with locator.override():
locator.register(Service, lambda: CustomService(), RegistrationMode.SINGLETON)
instance = locator.resolve(Service) # Returns CustomService
# Outside override, original registration restored
instance = locator.resolve(Service) # Returns Service| Mode | Behavior |
|---|---|
SINGLETON |
Single instance created at registration and reused for all resolutions. |
LAZY_SINGLETON |
Instance created on first resolution, then cached for subsequent calls. |
FACTORY |
Factory function called on every resolution to return a new instance. |
NotRegisteredError: Raised when a requested type or tag is not registered.RootScopeCloseError: Raised when attempting to close the root scope of aLocator.
While the global locator is sufficient for most cases, you can create custom locators or registries if you need isolated scopes or multiple independent DI containers:
from di import Locator, Registry
my_locator = Locator()
my_registry = Registry()