lagom icon indicating copy to clipboard operation
lagom copied to clipboard

ContextManager doesn't work with async, or maybe ExplicitContainer?

Open jeffsawatzky opened this issue 11 months ago • 1 comments

Trying to use the context container to create a "transaction" for the whole "request". Here is some code to give an example:

import asyncio
import dataclasses
from collections.abc import AsyncIterator
from typing import Any

import lagom


class Transaction:
    async def __aenter__(self) -> "Transaction":
        return self

    async def commit(self) -> None:
        return

    async def rollback(self) -> None:
        return

    async def close(self) -> None:
        return

    async def __aexit__(self, exc_type: type[BaseException] | None, *args: Any, **kwargs: Any) -> None:
        if exc_type is None:
            # Auto commit if there was no exception
            await self.commit()
        else:
            # Auto rollback is there was an exception
            await self.rollback()
        return None


@dataclasses.dataclass
class UserModel:
    user_id: int


class UserRepository:
    def __init__(self, transaction: Transaction) -> None:
        self._transaction = transaction

    async def find_user(self, user_id: int) -> UserModel:
        user = None
        async with self._transaction as t:
            # do some db stuff here, maybe create the user if it doesn't exist or something...
            user = UserModel(user_id=user_id)
        return user


class FindUserUseCase:
    def __init__(self, user_repo: UserRepository) -> None:
        self._user_repo = user_repo

    async def execute(self, user_id: int) -> UserModel:
        return await self._user_repo.find_user(user_id=user_id)


container = lagom.ExplicitContainer()


@lagom.context_dependency_definition(container)  # type: ignore[misc]
async def _get_a_transaction() -> AsyncIterator[Transaction]:
    transaction = Transaction()
    try:
        yield transaction
    finally:
        await transaction.close()


container[UserRepository] = lambda c: UserRepository(c[Transaction])

container[FindUserUseCase] = lambda c: FindUserUseCase(c[UserRepository])

context_container = lagom.ContextContainer(container=container, context_types=[], context_singletons=[Transaction])


@lagom.bind_to_container(context_container)
async def find_user(user_id: int, find_user_use_case: FindUserUseCase = lagom.injectable) -> None:
    user = await find_user_use_case.execute(user_id=user_id)
    print(user)


if __name__ == "__main__":
    asyncio.run(find_user(1))

When I run this I get:

lagom.exceptions.InvalidDependencyDefinition: A ContextManager[<class '__main__.Transaction'>] should be defined. This could be an Iterator[<class '__main__.Transaction'>] or Generator[<class '__main__.Transaction'>, None, None] with the @contextmanager decorator

Am I doing something wrong? If so, what?

jeffsawatzky avatar Mar 19 '24 04:03 jeffsawatzky