Skip to content

Suggestion: ASYNC119 when using an async context manager in an async generator #211

Closed
@alicederyn

Description

@alicederyn

Antipattern: The following code is unsafe:

async def yield_some_values():
    async with some_async_context_manager():
        for _ in range(5):
            ...
            yield some_value  # error: `yield` inside `async with` block

async def bad_method():
    async for value in yield_some_values():
        raise Exception("exiting early")

Explanation: If the iterator is not fully consumed, the cleanup path will only be triggered by the garbage collector, which will not allow the async cleanup logic to await (it will raise GeneratorExit when it tries). A similar issue arises if you try to await in a finally block in an async generator, or an except block that can intercept asyncio.exceptions.CancelledError.

The only exception to this that I'm aware of is if the async generator is decorated with @contextlib.asynccontextmanager.

Fix: I think unfortunately the only fix is to refactor the API to separate the context management from the iteration, e.g.

@contextlib.asynccontextmanager
async def yield_some_values_safely():
    async with some_async_context_manager():
        async def inner_iterator():
            for _ in range(5):
                ...
                yield some_value
        yield inner_iterator  # safe because this is in an asynccontextmanager

async def bad_method():
    async with yield_some_values_safely() as values:
        async for value in values:
            raise Exception("exiting early")

I'm not aware of any existing lint check for this antipattern (I may have missed it though!) — do the maintainers of this repo think it would be valuable?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions