ivankorobkov / python-inject

Python dependency injection
Apache License 2.0
694 stars 79 forks source link

AttributeError: '_AsyncGeneratorContextManager' object has no attribute 'execute' #94

Closed aryaniyaps closed 6 months ago

aryaniyaps commented 9 months ago

These are my dependencies:

from contextlib import asynccontextmanager, contextmanager
from typing import AsyncIterator, Iterator

import inject
from argon2 import PasswordHasher
from redis.asyncio import Redis, from_url
from sqlalchemy.ext.asyncio import AsyncConnection

from app.config import settings
from app.core.database import engine
from app.core.emails import EmailSender

@asynccontextmanager
async def get_database_connection() -> AsyncIterator[AsyncConnection]:
    """Get a database connection."""
    async with engine.begin() as connection:
        yield connection

@asynccontextmanager
async def get_redis_client() -> AsyncIterator[Redis]:
    """Get the redis client."""
    redis_client = from_url(
        url=str(settings.redis_url),
    )
    yield redis_client

    await redis_client.aclose()

@contextmanager
def get_email_sender() -> Iterator[EmailSender]:
    """Get the email sender."""
    email_sender = EmailSender(
        email_server=settings.email_server,
        email_from=settings.email_from,
    )
    yield email_sender
    email_sender.close_connection()

def app_config(binder: inject.Binder) -> None:
    """Configure dependencies for the binder."""
    binder.bind_to_constructor(
        EmailSender,
        get_email_sender,
    )
    binder.bind_to_constructor(
        Redis,
        get_redis_client,
    )
    binder.bind_to_provider(
        AsyncConnection,
        get_database_connection,
    )
    binder.bind(
        PasswordHasher,
        PasswordHasher(),
    )

inject.configure(app_config)

This is my UserRepo:

class UserRepo:
    _connection = inject.attr(AsyncConnection)
    _password_hasher = inject.attr(PasswordHasher)

    async def create_user(
        self,
        username: str,
        email: str,
        password: str,
    ) -> User:
        """Create a new user."""
        result = await self._connection.execute(
            insert(users_table)
            .values(
                username=username,
                email=email,
                # hash the password before storing
                password_hash=self.hash_password(
                    password=password,
                ),
            )
            .returning(*users_table.c),
        )
        user_row = result.one()
        return User.model_validate(user_row)

    def hash_password(self, password: str) -> str:
        """Hash the given password."""
        return self._password_hasher.hash(
            password=password,
        )

I am getting this error while accessing the UserRepo:

self = <app.users.repos.UserRepo object at 0x0000016D0F176810>, username = 'new_user', email = 'new@example.com', password = 'password'

    async def create_user(
        self,
        username: str,
        email: str,
        password: str,
    ) -> User:
        """Create a new user."""
>       result = await self._connection.execute(
            insert(users_table)
            .values(
                username=username,
                email=email,
                # hash the password before storing
                password_hash=self.hash_password(
                    password=password,
                ),
            )
            .returning(*users_table.c),
        )
E       AttributeError: '_AsyncGeneratorContextManager' object has no attribute 'execute'

app\users\repos.py:24: AttributeError

This UserRepo is used by a AuthService:

class AuthService:
    _auth_repo = inject.attr(AuthRepo)
    _user_repo = inject.attr(UserRepo)
    _password_hasher = inject.attr(PasswordHasher)

Which is accessed by my falcon resources like this:

class AuthResource:
    @inject.autoparams("auth_service")
    async def on_post_register(
        self,
        req: Request,
        resp: Response,
        auth_service: AuthService,
    ) -> None:
        """Register a new user."""
        data = await req.media
        result = await auth_service.register_user(
            data=RegisterUserInput.model_validate(data),
        )
        resp.media = result.model_dump(mode="json")
        resp.status = HTTP_201

How to fix this error? Im stuck as the documentation doesn't seem that clear!

ivankorobkov commented 9 months ago

Hi aryaniyaps,

Thank you for reporting a bug and providing all the info.

@fzyukio excuse me, could you check what's the problem here? Maybe you see the solution?

ivankorobkov commented 9 months ago

@aryaniyaps I'm sorry. I'll take a look in a couple of days. A little busy just now.

aryaniyaps commented 9 months ago

@ivankorobkov sure, take your time! It would be really useful to be able to use async contextmanagers fully though..

jeanlst commented 8 months ago

Any news on this @ivankorobkov ?

ivankorobkov commented 6 months ago

Released https://pypi.org/project/inject/5.2.1/