Loading...
Loading...
This skill should be used when the user asks to "implement dependency injection in Python", "use the dependency-injector library", "decouple Python components", "write testable Python services", or needs guidance on Inversion of Control, DI containers, provider types, and wiring in Python applications.
npx skill4agent add the-perfect-developer/the-perfect-opencode python-dependency-injectionclass UserNotifier:
def __init__(self):
self.email = EmailService() # hard-coded, untestable
def notify(self, msg):
self.email.send(msg)class UserNotifier:
def __init__(self, email_service: EmailService):
self.email_service = email_service # injected, swappable
def notify(self, msg):
self.email_service.send(msg)
notifier = UserNotifier(EmailService())| Style | How | When to use |
|---|---|---|
| Constructor | Pass via | Default; dependencies are required |
| Setter | Assign via method after construction | Optional dependencies |
| Method | Pass directly to the method call | One-off or per-call dependencies |
dependency-injectordependency-injectorpip install dependency-injectorfrom dependency_injector import containers, providers
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
api_client = providers.Singleton(
ApiClient,
api_key=config.api_key,
timeout=config.timeout,
)
service = providers.Factory(
Service,
api_client=api_client,
)| Provider | Behavior | Use case |
|---|---|---|
| Single shared instance for entire app lifetime | DB connections, API clients, caches |
| New instance on every call | Request handlers, per-operation objects |
| Reads from env vars, YAML, JSON, ini | App settings |
| Managed lifecycle with setup/teardown | DB sessions, file handles |
| Wraps any callable | Functions, class methods |
| Provides a fixed value | Constants, pre-built objects |
| Selects a provider based on config | Environment-based switching |
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
container = Container()
container.config.api_key.from_env("API_KEY", required=True)
container.config.timeout.from_env("TIMEOUT", as_=int, default=5)
# Also supports: .from_yaml(), .from_ini(), .from_dict(), .from_pydantic()@injectProvide[...]container.wire()from dependency_injector.wiring import inject, Provide
@inject
def main(service: Service = Provide[Container.service]) -> None:
service.do_work()
if __name__ == "__main__":
container = Container()
container.config.from_env(...)
container.wire(modules=[__name__])
main() # service is injected automaticallycontainer.wire(packages=["myapp"])# In tests
with container.api_client.override(mock.Mock()):
main() # the mock is injected insteaddependency-injectordef test_service_behavior():
container = Container()
container.db.override(providers.Factory(FakeDB))
service = container.service()
assert service.get_data() == "expected"dependency_overridesapp.dependency_overrides[get_db_session] = lambda: FakeDBSession()Resourcefrom dependency_injector import resources
class DatabaseResource(resources.Resource):
def init(self) -> Database:
db = Database(url=self.config.db_url())
db.connect()
return db
def shutdown(self, db: Database) -> None:
db.disconnect()
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
db = providers.Resource(DatabaseResource, config=config)
# Usage
container = Container()
container.init_resources()
# ... use container.db() ...
container.shutdown_resources()Depends()# dependency-injector async resource
class AsyncDBResource(resources.AsyncResource):
async def init(self) -> AsyncDB:
db = AsyncDB()
await db.connect()
return db
async def shutdown(self, db: AsyncDB) -> None:
await db.disconnect()
# FastAPI native async dependency
async def get_db() -> AsyncGenerator[AsyncSession, None]:
async with async_session() as session:
yield session
@app.get("/items")
async def read_items(db: AsyncSession = Depends(get_db)):
...from abc import ABC, abstractmethod
class MessageSender(ABC):
@abstractmethod
def send(self, message: str) -> None: ...
class EmailSender(MessageSender):
def send(self, message: str) -> None:
print(f"Email: {message}")
class SMSSender(MessageSender):
def send(self, message: str) -> None:
print(f"SMS: {message}")
# Container switches implementation without changing consumer
class Container(containers.DeclarativeContainer):
sender = providers.Factory(EmailSender) # swap to SMSSender anytimeSingletonFactorypoetry.lock| Anti-pattern | Problem | Fix |
|---|---|---|
| Service Locator | | Inject explicitly via constructor or |
| Over-injection | 10+ constructor params | Split into smaller, focused classes |
| Tight coupling | | Accept dependency as parameter |
| Scope mismanagement | | Use |
| Monkey-patching in tests | | Use |
pip install dependency-injector # base
pip install "dependency-injector[yaml]" # + YAML config support
pip install "dependency-injector[pydantic2]" # + Pydantic v2 settings# Minimal working container
from dependency_injector import containers, providers
from dependency_injector.wiring import inject, Provide
class Container(containers.DeclarativeContainer):
config = providers.Configuration()
service = providers.Factory(MyService, setting=config.setting)
@inject
def handler(svc: MyService = Provide[Container.service]):
svc.run()
container = Container()
container.config.from_dict({"setting": "value"})
container.wire(modules=[__name__])
handler()references/patterns.mdreferences/framework-integration.md