python-dependency-injector
python-dependency-injector copied to clipboard
Add context local resource
Closes Issue #728 , #760 #907 Relates to #699
Added a ContextLocalResource class that inherits from Resource and overrides it so that the actual _resource and _shutdowner are stored and retrieved via ContextVar.
This allows each context (such as a FastAPI request) to have its own instance of the resource.
Since FastAPI creates a new context per request, each request now gets its own context-scoped resource.
Because the class extends Resource, the Closing marker continues to work as expected.
Demo Run the following API, then do concurrent requests.
from dependency_injector import containers, providers
from dependency_injector.wiring import Closing, Provide, inject
from fastapi import Depends, FastAPI
from uuid import uuid4
global_list = []
class AsyncSessionLocal:
def __init__(self):
self.id = uuid4()
async def __aenter__(self):
print("entering session")
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("closing session")
async def execute(self, user_input):
return "executing " + user_input
async def get_session() -> AsyncSessionLocal:
print("start session")
async with AsyncSessionLocal() as session:
yield session
print("close session")
app = FastAPI()
class Container(containers.DeclarativeContainer):
db = providers.ContextLocalResource(get_session)
@app.get("/")
@inject
async def index(db: AsyncSessionLocal = Depends(Closing[Provide["db"]])):
global global_list
if db.id in global_list:
raise Exception("The db session is already being used") # never reaches here
global_list.append(db.id)
res = await db.execute("SELECT 1")
return str(res)
if __name__ == "__main__":
import uvicorn
container = Container()
container.wire(modules=["__main__"])
uvicorn.run(app, host="localhost", port=8000)
container.unwire()