Skip to content

Context Local Scope-Level Resource/Singleton/... #907

@XKTZ

Description

@XKTZ

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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions