python-dependency-injector icon indicating copy to clipboard operation
python-dependency-injector copied to clipboard

Add context local resource

Open elina-israyelyan opened this issue 1 month ago • 2 comments

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()

elina-israyelyan avatar Oct 22 '25 22:10 elina-israyelyan