ets-labs/python-dependency-injector

Impossible to integrate with context managers?

Closed this issue · 3 comments

I am trying to integrate the latest version of the library in my project, and i have faced the issue i can't find a solution for - i cannot make the cleanup work for usage mid-function.

Most of the time my usage of containers/providers is highly conventional and matches FastAPI+SQLAlchemy example. So here's some simplified examples. The main container and the database containers are declared this way:

import example_routes

class DBManager:
    """Manages database connection and migrations."""
    _engine: AsyncEngine = None
    _sessionmaker: async_sessionmaker = None
    
    ... # connect and dispose logic

async def get_session():
    async with DBManager._sessionmaker() as session:
        session: AsyncSession
        try:
            yield session
            await session.commit()
        except Exception as e:
            await session.rollback()
            logger.critical(e)
            raise
        finally:
            await session.close()

class DatabaseContainer(containers.DeclarativeContainer):
    session_factory = providers.Resource(
        get_session
    )
        transaction_service = providers.Factory(
        TxService,
        session=session_factory
    )


class MainContainer(containers.DeclarativeContainer):
    wiring_config = containers.WiringConfiguration(
        modules=[
            example_routes
        ]
    )

    config = providers.Configuration()

    db = providers.Container(
        DatabaseContainer,
        config=config.run
    )

Meanwhile the usage mostly works like that:

@inject
async def some_business_logic(
    tx_service: TxService = Closing[Provide[MainContainer.db.transaction_service]],
):
    ...

HOWEVER.

I have a function with a long execution time, which makes several calls to the database and each of these calls needs to be its own session (cause i update database records and need to do that in real-time).
When i need to use the service in some kind of limited span inside of a function, i encounter a bug(?) where it does not trigger cleanup after the session was used. i have tried several approaches and none of them seem to work. Here is what i want to do:

async def long_function():
    async with MainContainer.db.transaction_service as service:
        await service.funcion_a()
    # session is commited and closed during cleanup
    # more logic

    async with MainContainer.db.transaction_service as service:
        await service.function_b()
    # new session is commited and closed during cleanup

I have tried creating service context managed functions, somewhat like below, but they do not trigger cleanup

@asynccontextmanager
@inject
async def get_tx_service_ctx(
    tx_service: TxService = Closing[Provide[MainContainer.db.session_factory]]
) -> AsyncGenerator[TxService, None]:
    # somewhy under contextmanager container provides a future
    serv = await tx_service 
    yield serv
    print(serv)

The print at the end is triggered, but the cleanup of a session doesn't seem to be. What should i do? Can i make some kind of a workaround for this? I can't seem to find an alternative way in the docs nor in the examples

Hello. This is a known issue. Good news I'm working on solving it right now. ETA approximately this or next weekend.

Thanks a lot for your time! Hope it goes well)

v4.48.0 now supports context managers in Resource providers.