-
-
Notifications
You must be signed in to change notification settings - Fork 334
Description
Hi!
I would like to manage a scope-managed resource with a possible fastapi request. If I write something like:
class MyService:
def __init__(self, session: Callable[[], Awaitable[int]]) -> None:
self._session = session
async def dosth(self):
print(f"Session ID: {await self._session()}")
"""
I expect
async def sess():
print("I am starting")
yield uuid.uuid4().int
print("I am ending")
"""
async def sess():
print("I am starting")
return uuid.uuid4().int
class Container(DeclarativeContainer):
_sess = providers.Factory(sess)
_ctx = providers.ContextLocalSingleton(_sess)
my_service = providers.Resource(MyService, session=_ctx.provider)
container = Container()
app = FastAPI(lifespan=Lifespan(container=container))
@app.get("/")
@inject
async def index(
service: Annotated[MyService, Depends(Provide[Container.my_service])],
):
await service.dosth()
await service.dosth()
return 1
container.wire(modules=[__name__])
client = TestClient(app)
client.get("/")
client.get("/")
print("I am ok")
The code is maximum I would be able to go for now - the desired behaviour is: the two service.dosth
calls print same ID in one session, but print different ID in two sessions. So without considering the lifetime the thing is already doable.
However, if we add an extra constraint to the code, which is in the comment section, that we also hope we are able to commit the stuff at the end (with/without a contextmanager, just like waht the Resource
do), it seems become difficult. Because it seems ContextLocal seems not being able to catch & handle an AsyncContextManager / Generator. The demand of this seems to be general, where the user may need a yield
a database session every request:
async def get_session(engine):
async with AsyncSession(engine) as session():
async with session.begin():
yield session
I have tried a few possible ways:
- Simply use a Factory won't give same ID even in one session, and Factory seems can't catch a correct contextmanager either
- Use Resource can work but Resource is only for global scope, not request level scope. So it can't work nicely in my scenario
__del__
might also works, but it is fully give the choice to python's GC
May I ask if there are other ways possibly can solve this problem in the scenario? Thanks a lot!