Asyncio equivalent of trio/anyio move_on_after

It’s currently difficult to identify a timeout from asyncio.timeout or from other TimeoutError in the context manager body

I’ve seen code in the wild that incorrectly does:

try:
    async with asyncio.timeout(1):
        await might_raise_timeout_error()
except TimeoutError:
    raise SystemFailed("1 second timeout expired")

But it’s possible that might_raise_timeout_error will raise its own TimeoutError for another reason (eg before the 1 second timeout)

I’d like to see an exc flag to asyncio.timeout so you can do:

async with asyncio.timeout(1, exc=SystemFailed("timeout occurred")):
    await etc()

Or for move_on_after

async with asyncio.timeout(1, exc=None):
    await etc()

Currently the code to do it in consuming code is possible, but it is a bit complicated:

@contextlib.asynccontextmanager
async def move_on_after(timeout):
    timed_out = False
    try:
        async with timeout:
            try:
                yield
            except TimeoutError:
                timed_out = True
                raise
    except TimeoutError as e:
        if timed_out:
            raise
@contextlib.asynccontextmanager
async def raise_after(timeout, exc):
    timed_out = False
    try:
        async with timeout:
            try:
                yield
            except TimeoutError:
                timed_out = True
                raise
    except TimeoutError as e:
        if timed_out:
            raise
        else if exc is not None:
            try:
                raise exc from e
            finally:
                del exc
2 Likes

Is there any specific reason not to use try/except?

try:
    await might_raise_timeout_error()
except TimeoutError:
    raise SystemFailed("A different timeout error occurred")

it’s possible that func will raise its own SomeException for another reason.

I have seen this argument used in other threads as well, and I feel like I might be missing something.

I would consider that if something I’m calling times out at a place where I’m capping the time to wait before continuing, that it probably doesn’t change how I need to proceed.

if it does, I would intentionally write the more verbose version to call attention to the fact that we are handling a lower level timeout differently, and would probably not re-raise but handle it appropriately at callsite.

1 Like