Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wireup.errors.WireupError: Cannot construct async objects fron a non-async context. #55

Open
almeynman opened this issue Jan 28, 2025 · 3 comments

Comments

@almeynman
Copy link

almeynman commented Jan 28, 2025

Hello, I have created an async iterator for running transactions, here are the services involved:

pg_client.py

@service(lifetime=ServiceLifetime.TRANSIENT)
@dataclass
class PgClient:
    _pool: Pool

    @contextlib.asynccontextmanager
    async def run_transaction(self, connection: Connection | None = None) -> AsyncIterator[Connection]:
        if connection is not None:
            yield connection
            return
        async with self._pool.acquire() as conn, conn.transaction():
            yield conn


@service(lifetime=ServiceLifetime.SINGLETON)
async def create_pg_pool(env: Env) -> AsyncIterator[Pool]:
    # configure connection pool https://magicstack.github.io/asyncpg/current/usage.html#example-automatic-json-conversion
    user = env.POSTGRES_USER.get_secret_value()
    password = urllib.parse.quote_plus(env.POSTGRES_PASSWORD.get_secret_value())
    host = env.POSTGRES_HOST.get_secret_value()
    port = env.POSTGRES_PORT.get_secret_value()
    db_name = env.POSTGRES_DB_NAME.get_secret_value()

    dsn = f"postgresql://{user}:{password}@{host}:{port}/{db_name}"

    async def init(conn: Connection) -> None:
        await conn.set_type_codec("uuid", encoder=str, decoder=str, schema="pg_catalog")
        await register_vector(conn, schema="extensions")

    pool = await create_pool(dsn=dsn, init=init, min_size=5, max_size=20)
    yield pool
    await pool.close()

Then to instantiate in fastapi applicaiton I call container.warmup() in lifespan like so

def create_app() -> FastAPI:
    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
        container = app.state.container
        container.warmup()

        yield

        await container.aclose()

    app = FastAPI(lifespan=lifespan, debug=env.ENV != "production")

    container = create_container()
    app.state.container = container
    wireup.integration.fastapi.setup(app.state.container, app)

    return app

This throws wireup.errors.WireupError: Cannot construct async objects fron a non-async context. error

I managed to workaround it by awaiting for pg_client explicitly, like so

def create_app() -> FastAPI:
    @asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
        container = app.state.container

        # Fixes "wireup.errors.WireupError: Cannot construct async objects fron a non-async context."
        @container.autowire
        async def get_pg_client(pg_client: PgClient) -> PgClient:
            return pg_client

        await get_pg_client()
        container.warmup()

        yield

        await container.aclose()

    app = FastAPI(lifespan=lifespan, debug=env.ENV != "production")

    container = create_container()
    app.state.container = container
    wireup.integration.fastapi.setup(app.state.container, app)

    return app

However not sure if it's smth with the lib or I am just doing smth wrong

@maldoinc
Copy link
Owner

Hi, the issue is that warmup_container is a regular function and your create_pg_pool resource is an async one so it cannot be created from the warmup_container call. The fix you did works because the autowired get_pg_client is also async and as such wireup can await for pg pool.

#45 is precisely about this. To have an async verison of warmup_container use cases where underlying resources may be async factories. In your case I'd just remove the call to warmup_container and let the container create it when necessary.

@almeynman
Copy link
Author

Cool, thanks. I will leave this workaround for now and will wait for the #45

@maldoinc
Copy link
Owner

maldoinc commented Feb 3, 2025

Hi, with the new 0.16.0 version you can now just do await container.aget(PgClient).

from wireup.integration.fastapi import get_container

@asynccontextmanager
    async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
        container = get_container(app)
        await container.aget(PgClient)

        yield

        await container.aclose()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants