-
-
Notifications
You must be signed in to change notification settings - Fork 31.1k
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
add contextlib.AsyncExitStack #73488
Comments
ExitStack is a really useful class and would be a great to have an async version. I've gone ahead and created an implementation based on the existing Python 3.5.2 implementation. Let me know what you guys think. I think it would be possible to combine most of the two classes together if you guys think it would be useful. Let me know if I can/should create a github PR and where to do that. |
created gist: https://gist.github.com/thehesiod/b8442ed50e27a23524435a22f10c04a0 I've now updated the imple to support both __aenter__/aexit and __enter__/exit so I don't need two ExitStacks |
An example from "real life": I recently needed this when implementing a demo of dining philosophers using asyncio (when the order of locks should depend on comparison of fork ids, not be static). Of course, it wasn't hard to implement it directly, but still, it would be nice if I had it in stdlib. Also, yes, it would probably be nicer if there was only one ExitStack with push_async and similar methods. |
I quite like the idea of enhancing the existing ExitStack to also handle asynchronous operations rather than having completely independent implementations. However, a separate object may end up being simpler in practice as it allows developers to more explicitly declare their intent of writing async-friendly code. Sketching out what a combined implementation based on the current ExitStack might look like:
That's really quite messy and hard to explain, and makes async code look quite different from the corresponding synchronous code. The repeated checks of By contrast, a dedicated AsyncExitStack object can:
|
Thanks for the feedback Nick! If I get a chance I'll see about refactoring my gist into a base class and two sub-classes with the async supporting non-async but not vice-versa. I think it will be cleaner. Sorry I didn't spend too much effort on the existing gist as I tried quickly layering on async support to move on to my primary task. After doing that I noticed that the code could use some refactoring, but only after taking the time deconstructing the impl to understand how the code works. |
Alexander, You don't need asyncio.isocoroutinefunction. Please use inspect.iscoroutinefunction and inspect.iscoroutine. Nick, I'm +1 to have a separate class AsyncExitStack.
I would add a separate set of methods prefixed with 'a' to handle async context managers.
I'd only have one coroutine 'aclose()' that would internally close sync and async context managers in the right order.
I'd use the 'a' prefix. We use it already to name magic methods: __aenter__, __aexit__, __aiter__.
I'd favour a more explicit approach: separate methods for handling sync and async. Also, speaking about asyncio dependancy -- we shouldn't have it. Using asyncio.iscoroutinefunction is unnecessary, inspect.iscoroutinefunction should be used instead. |
Looking at the code: we don't need to use iscoroutinefunction at all. If we had separate methods for sync and async context managers, you can store the flag if a function is async or sync along with it. So _exit_callbacks should store tuples asyncio.iscoroutinefunction differs from inspect.iscoroutinefunction a little bit to support asyncio-specific debug coroutine wrapper functions. We should avoid using any version of iscoroutinefunction here. |
ok I've updated the gist with a base class and sync + async sub-classes. The way it worked out I think is nice because we can have the same method names across both sync+async. Let me know what you guys think! btw, it seems the test_dont_reraise_RuntimeError test hangs even with the release version. |
Overall, the approach looks fine. Alexander, do you want to make a PR to start working on adding this to 3.7? |
I'm not sure about type() to get a class object and calling __aenter__, __aexit__ through it: that makes it hard to mock these classes as Mock's spec= relies on __class__ and type() seem to ignore it (learned it a hard way. Yury, I could take a second look and try to change this into a patch if that's OK. |
Looking up __dunder__ methods on the class is how it should be done as that's how the rest of Python works. And that's how ExitStack is currently implemented for synchronous "with" blocks. We won't be able to change this behaviour even if we wanted to, so it stays. Here's an example: class Foo:
def __init__(self):
self.__aenter__ = ...
self.__aexit__ = ... If we implement AsyncExitStack to lookup __anter__ directly on the object, then the above Foo class would be supported by it, but at the same time rejected by the 'async with' statement.
By all means you can submit a PR! |
Perhaps unittest.mock (or type) needs to be adjusted to allow mocking via spec= without subclassing?
I'll take a look then. |
Maybe. You should try to find discussions around this topic on python mailing lists and this tracker. If you find nothing then feel free to open an issue and add Michael Foord to nosy list. |
While it *may* be possible to do something simpler for test purposes where performance isn't a major concern, fully supporting type() level mocking basically requires bringing the equivalent of wrapt object proxies into the standard library: https://wrapt.readthedocs.io/en/latest/wrappers.html#object-proxy I actually think we *should* do that, and occasionally bug Graham Dumpleton about it, but while he's not opposed to the idea, it's also something that would take quite a bit of work (since odd edge cases that are tolerable in an opt-in third party module would probably need to be addressed for a standard library version). For test cases like AsyncExitStack though, we instead just use custom type definitions, rather than the unittest.mock module. Autospec'ed mocks are most attractive when we're dealing with object interfaces that are subject to a high rate of churn (since they make the tests more self-adjusting), and that isn't the case here: Python's syntactic support protocols rarely change, and when they do, preserving backwards compatibility with existing classes is typically a key requirement. |
let me know if I need to do anything |
Alexander, Did you want to submit a PR for this? |
Charyl, I made the PR. Where is the AbstractAsyncContextManager? I see that typing.py references it, but there is no actual implementation. |
Looks like AbstractAsyncContextManager is defined in issue bpo-30241. |
Explicitly noting some API design decisions from the review of #4790:
|
Thank you Alexander and Ilya! |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: