Support async contextmanagers in contextlib.suppress

Now that Python supports chaining multiple context managers with a single with or async with statement, I think that it would be nice if contextlib.suppress would also support both sync and async context managers.

from contextlib import suppress
import aiofiles
import json

with suppress(FileNotFoundError), open("config.json") as f:
    config = json.load(f) 
    # do smthg that is not critical
 
# TypeError TypeError: 'suppress' object does not support the asynchronous context manager protocol
async with suppress(FileNotFoundError), aiofiles.open("config.json") as f: 
    content = await f.read()
    config = json.loads(content)
    # do smthg that is not critical

As we see suppress doesn’t implement __aenter__ / __aexit__, so it’s not usable in async with, even though it feels it should work just fine.

Of course we can currently do:

# compliant way
with suppress(FileNotFoundError)
    async with aiofiles.open("config.json") as f: 
        content = await f.read()
        config = json.loads(content)
        # do smthg that is not critical

And this is fine, but i don’t find it as elegant. specially when the suppress may break a block of 2-3 async context managers that could be merged together, Anyone else sees this as a good idea?

See also: Allow mixed sync/async context managers in `async with` statements

Is not the same thing, he proposes to allow always to mix the syntax, which is confusing because because if you do that, we can’t have two different behaviors for aenter and __enter__and discriminate them,

This doesn’t apply to suppress though, as there is only one expected behavior both sync or async.

This was also proposed:

Then you could have this:

with (
    suppress(FileNotFoundError),
    async aiofiles.open("config.json") as f
): 
2 Likes

That option is indeed better, but is still far more disruptive than my suggestion.

I only suggest to add aenter, aexit to a class in the std library, and that suggests to change the language itself :sweat_smile: Of course if that materializes this won’t be needed, but realistically I don’t think that idea it will happen anytime soon anyways… So why not to do this ?

1 Like

Both your idea and the proposed syntax extension will happen in 3.15 at the earliest, so I don’t see much of a case for adding just your smaller idea. Maybe if the PEP is rejected.

3 Likes

My objection is the same as to the “as_acm() helper” rejected in PEP 806: adding __aenter__ would either break the transitive property that ‘async with marks a location where it is possible to switch or cancel tasks’, or else lock in a particular async framework as the only option.

As a Trio enthusiast and maintainer, I’m not keen on either option! Better to make mixing sync and async context managers more ergonomic :slight_smile:

Hi, thanks for the feedback, but maybe could you elaborate more on that please?

I don’t see how will break any property that is not broken already by any class that implements both __enter__ and __aenter__, I’m not suggestiong that __aenter__ in “suppress” should have any special treatment, so i would not be surprise if python switch or cancel a task there.

But yes I also think is fair to wait for PEP 806 to be accepted / rejected to implement this or not.

The trick is that the async framework can only switch or cancel a task if you await all the way back into it; unlike the operating system thread scheduler. Trio’s docs offer a nice overview.

So it’s fine to define both __enter__ and __aenter__, but in order to act as a checkpoint the latter has to e.g. await <x>.sleep(0). And how do we decide which framework x should be? For third-party libraries, anyio.sleep(0) will automatically use asyncio or trio as appropriate, but that’s not really an option for the standard library…

Thanks! now i understand your point way better. To be honest I would also not be bother if there is not a call to sleep at all, but i guess this is what you said that would may come unexpected behaviour (breaking the transitive property) , given that there will be at least another context manager that will implement it I don’t see it as much as an issue, but In any case we can check what is the resolution of the other PEP to see if this makes senses in the first place.