Simple, lightweight dependency injection for synchronous and asynchronous code, with request-scoped context useful for web frameworks such as FastAPI.
- Register classes and factories as
SINGLETON,TRANSIENTorREQUESTscope. - Support for synchronous and asynchronous providers (functions / async functions).
Inject(...)helper for explicit provider parameters.Container.injectdecorator to auto-inject parameters into callables.- FastAPI integration:
Container.Depends(...)andRequestScopeMiddleware.
Or install from a distribution package (if published):
# PIP
python -m pip install pydepin
# UV
uv add pydepinRequirements: Python 3.9+.
Create a Container, register providers and resolve dependencies.
from depin import Container, Inject, Scope
DI = Container()
class Config:
def __init__(self):
self.value = 100
def config_provider():
return Config()
def service(config: Config):
return config.value * 2
# register providers
DI.bind(source=config_provider, scope=Scope.SINGLETON)
DI.bind(source=service, scope=Scope.SINGLETON)
result = DI.get(service) # -> 200Container— main entry point. Use it to register providers and resolve values.Scope— enumeration withSINGLETON,TRANSIENTandREQUEST.SINGLETON: one instance shared during container lifetime.TRANSIENT: a new instance produced on each resolution.REQUEST: request-scoped lifecycle (requiresRequestScopeServicecontext).
Inject(provider)— used as a default value for function/constructor parameters to explicitly point to a provider.Container.inject— decorator that wraps a function and automatically fills injectable parameters (by type-hint orInject(...)). Works for sync and async.
You can register providers in a few different ways:
- Using
bind(...)with a class or function literal:
DI.bind(source=MyClass, scope=DI.Scope.SINGLETON)
DI.bind(source=my_factory_fn, scope=DI.Scope.SINGLETON)- Using the
@register(...)decorator (convenient for modules):
@DI.register(DI.Scope.TRANSIENT)
def random_id():
import uuid
return uuid.uuid4().hex
@DI.register(DI.Scope.REQUEST)
class UserService:
def __init__(self, repo: UserRepo, request: Request):
...Use Inject(provider) when you want to override the type hint and point to
another provider directly:
from depin import Inject
def get_string():
return 'from_provider'
class Service:
def __init__(self, value: int = Inject(get_string)):
self.value = value
DI.bind(source=get_string, scope=DI.Scope.SINGLETON)
DI.bind(source=Service, scope=DI.Scope.SINGLETON)
s = DI.get(Service)
assert s.value == 'from_provider'Request scope supports generator / async-generator providers which are entered when first resolved during a request and cleaned up when the request scope ends.
Example (from example/dependencies/database.py):
from contextlib import asynccontextmanager
from depin import Inject, Scope
@DI.register(Scope.SINGLETON)
async def db_engine():
return Engine()
@DI.register(Scope.REQUEST)
async def db_session(engine: Engine = Inject(db_engine), session_id: str = Inject(random_id)):
async with db_session_ctx(engine, session_id) as session:
yield session
@asynccontextmanager
async def db_session_ctx(engine: Engine, session_id: str):
session = Session(engine, session_id)
try:
yield session
finally:
# cleanup
passThe container will call __aenter__ / __enter__ for request-scoped
generator providers and store the created resource in the current request store.
To use request scope with FastAPI, add the RequestScopeMiddleware from
depin.extensions.fastapi before defining routes and use Container.Depends
to get FastAPI-compatible dependencies that call into the container.
from fastapi import FastAPI
from depin import Container
from depin.extensions.fastapi import RequestScopeMiddleware
DI = Container()
app = FastAPI()
app.add_middleware(RequestScopeMiddleware)
@DI.register(DI.Scope.REQUEST)
class UserService:
def __init__(self, request: Request):
self.request = request
@app.get('/')
async def index(s: UserService = DI.Depends(UserService)):
# `s` is resolved from the container using the request-scoped store
return {'message': 'ok'}Alternatively you can call RequestScopeService directly from providers to
access the currently active Request instance.
Container.get(t)— synchronous resolution (raises when provider is async).Container.get_async(t)— asynchronous resolution that awaits async providers.Container.inject(func)— returns a wrapped callable that auto-injects dependencies by type hints andInject(...)defaults.Container.Depends(type_or_provider)— returns a FastAPIDependswrapper.
- See the
examplefolder for a complete FastAPI example integrating request scope, async database sessions, and services. - See
tests/for unit tests showing usage patterns (provider functions,Injectparam, decorators and scopes).
From the repository root run:
python example/app.pyOpen http://localhost:8001 to exercise the sample endpoints.