Dependency Injector
Dependency Injector is a dependency injection framework for Python. It helps implement the dependency injection principle, offering features like providers (Factory, Singleton, Callable, Configuration, Resource), declarative and dynamic containers, and wiring for integration with frameworks like Django, Flask, and FastAPI. It is mature, production-ready, and optimized for performance with Cython.
Warnings
- breaking Python 3.7 support was dropped in version 4.47.0. Users on Python 3.7 or older must upgrade their Python version or stay on an older `dependency-injector` release.
- gotcha Using `@inject` decorator without `Provide[...]` markers for parameters will produce a warning since version 4.48.1. This means the framework will not automatically infer which provider to use without the explicit marker.
- gotcha Pydantic v2 deprecation warnings could trigger in `dependency-injector` versions prior to 4.49.0 when using Pydantic for configuration. This was a compatibility issue.
- gotcha Incorrect monkeypatching during `container.wire()` in versions prior to 4.47.0 could violate Method Resolution Order (MRO) in some classes, leading to unexpected behavior.
- gotcha A common anti-pattern in Dependency Injection is having longer-lived services (e.g., Singletons) depend on shorter-lived services (e.g., Factories, Resources intended per-request). This 'captive dependency' can lead to stale data, resource leaks, or unexpected behavior in concurrent applications.
- gotcha Performing blocking I/O or computationally heavy operations within Provider definitions or Module `configure` methods can introduce performance bottlenecks or deadlocks, as `dependency-injector` uses internal locks for thread safety during container initialization.
Install
-
pip install dependency-injector
Imports
- containers
from dependency_injector import containers
- providers
from dependency_injector import providers
- Provide
from dependency_injector.wiring import Provide
- inject
from dependency_injector.wiring import inject
Quickstart
import os
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject
class ConfigService:
def __init__(self, api_key: str):
self.api_key = api_key
def get_api_key(self) -> str:
return self.api_key
class MyService:
def __init__(self, config_service: ConfigService):
self.config_service = config_service
def do_something(self) -> str:
api_key = self.config_service.get_api_key()
return f"Doing something with API Key: {api_key[:4]}..."
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
config_service = providers.Singleton(
ConfigService,
api_key=config.api_key
)
my_service = providers.Factory(
MyService,
config_service=config_service
)
@inject
def main_app_function(
service: MyService = Provide[Container.my_service],
):
print(service.do_something())
if __name__ == '__main__':
container = Container()
# Load configuration from environment variable (or .env file)
container.config.api_key.from_env('MY_APP_API_KEY', as_=str, default='default_key_1234567890')
# Example: Override during testing or development
# container.config.api_key.override('test_key_abcde')
container.wire(modules=[__name__])
# Set an environment variable for the example to work
os.environ['MY_APP_API_KEY'] = os.environ.get('MY_APP_API_KEY', 'example_api_key_12345')
main_app_function()
# Clean up environment variable (optional)
del os.environ['MY_APP_API_KEY']