msg285687 - (view) |
Author: Alexander Mohr (thehesiod) * |
Date: 2017-01-17 22:49 |
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.
|
msg286380 - (view) |
Author: Alexander Mohr (thehesiod) * |
Date: 2017-01-27 18:07 |
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
|
msg286411 - (view) |
Author: Vedran Čačić (veky) * |
Date: 2017-01-28 14:16 |
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.
|
msg288735 - (view) |
Author: Alyssa Coghlan (ncoghlan) * |
Date: 2017-03-01 05:01 |
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:
- add suitable `__aenter__` and `__aexit__` implementations
- add `enter_context_async` (async def, called with await)
- add `push_aexit` (normal def, but given function is called with await)
- add `coroutine_callback` (normal def, but given coroutine is called with await)
- add `close_async` (async def, called with await)
- add a new internal state flag `_allow_async`. This would default to True, but be set to False when the synchronous `__enter__` is called. When it's false, attempting to register an async callback would raise RuntimeError. `__enter__` would also need to check any previously register contexts and callbacks, and complain if any of them require the use of `Await`
- keep track of which callbacks should be invoked as synchronous calls and which should be called via await
- update `close` to raise RuntimeError if it's asked to clean up asynchronous resources
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 `self._allow_async` and `iscoroutinefunction` would also slow down existing synchronous code for no specific end user benefit.
By contrast, a dedicated AsyncExitStack object can:
- assume everything passed to it is a coroutine or an async context manager by default
- always require close() to be called via await
- either add synchronous variants of the default-to-async methods (`enter_context_sync`, `push_sync`, `callback_sync`), or else make them auto-adapt to handle both synchronous and asynchronous inputs
- rather than using `iscoroutinefunction`, instead always invoke callbacks with await, and wrap synchronous callbacks supplied using the above methods in simple one-shot iterators
|
msg288744 - (view) |
Author: Alexander Mohr (thehesiod) * |
Date: 2017-03-01 08:37 |
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.
|
msg288761 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2017-03-01 15:45 |
Alexander,
You don't need asyncio.isocoroutinefunction. Please use inspect.iscoroutinefunction and inspect.iscoroutine.
Nick,
I'm +1 to have a separate class AsyncExitStack.
> - assume everything passed to it is a coroutine or an async context manager by default
I would add a separate set of methods prefixed with 'a' to handle async context managers.
> - always require close() to be called via await
I'd only have one coroutine 'aclose()' that would internally close sync and async context managers in the right order.
> - either add synchronous variants of the default-to-async methods (`enter_context_sync`, `push_sync`, `callback_sync`), or else make them auto-adapt to handle both synchronous and asynchronous inputs
I'd use the 'a' prefix. We use it already to name magic methods: __aenter__, __aexit__, __aiter__.
> - rather than using `iscoroutinefunction`, instead always invoke callbacks with await, and wrap synchronous callbacks supplied using the above methods in simple one-shot iterators
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.
|
msg288762 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2017-03-01 16:06 |
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 `(is_async, cb)`, instead of just `cb`.
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.
|
msg290674 - (view) |
Author: Alexander Mohr (thehesiod) * |
Date: 2017-03-28 01:42 |
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.
|
msg293138 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2017-05-05 19:40 |
Overall, the approach looks fine. Alexander, do you want to make a PR to start working on adding this to 3.7?
|
msg300572 - (view) |
Author: Ilya Kulakov (Ilya.Kulakov) * |
Date: 2017-08-19 08:15 |
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.
|
msg300586 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2017-08-19 16:44 |
> 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.
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.
> Yury, I could take a second look and try to change this into a patch if that's OK.
By all means you can submit a PR!
|
msg300588 - (view) |
Author: Ilya Kulakov (Ilya.Kulakov) * |
Date: 2017-08-19 17:02 |
> but at the same time rejected by the 'async with' statement.
Perhaps unittest.mock (or type) needs to be adjusted to allow mocking via spec= without subclassing?
> By all means you can submit a PR!
I'll take a look then.
|
msg300589 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2017-08-19 17:04 |
>> but at the same time rejected by the 'async with' statement.
> Perhaps unittest.mock (or type) needs to be adjusted to allow mocking via spec= without subclassing?
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.
|
msg300595 - (view) |
Author: Alyssa Coghlan (ncoghlan) * |
Date: 2017-08-20 04:07 |
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.
|
msg301004 - (view) |
Author: Alexander Mohr (thehesiod) * |
Date: 2017-08-30 00:05 |
let me know if I need to do anything
|
msg307970 - (view) |
Author: Cheryl Sabella (cheryl.sabella) * |
Date: 2017-12-10 18:54 |
Alexander,
Did you want to submit a PR for this?
|
msg308014 - (view) |
Author: Ilya Kulakov (Ilya.Kulakov) * |
Date: 2017-12-11 06:00 |
Charyl, I made the PR.
Where is the AbstractAsyncContextManager? I see that typing.py references it, but there is no actual implementation.
|
msg308046 - (view) |
Author: Cheryl Sabella (cheryl.sabella) * |
Date: 2017-12-11 14:48 |
Looks like AbstractAsyncContextManager is defined in issue #30241.
|
msg310353 - (view) |
Author: Alyssa Coghlan (ncoghlan) * |
Date: 2018-01-20 15:43 |
Explicitly noting some API design decisions from the review of https://github.com/python/cpython/pull/4790:
1. AsyncExitStack will define __aenter__ and __aexit__ but *not* __enter__ and __exit. This makes it unambiguous which form of with statement you need to be using for a given stack.
2. AsyncExitStack will use the same methods to add synchronous context managers as ExitStack does
3. The only common method with clearly distinct external behaviour will be stack.close(), which will be a coroutine in the AsyncExitStack case (the other common methods will all still be synchronous in AsyncExitStack)
4. AsyncExitStack will gain 3 new methods for working with async context managers:
- enter_async_context(cm): coroutine to enter an async context manager and adds its __aexit__ to the context stack if __aenter__ succeeds
- push_async_exit(cm_or_exit): push an exit callback directly onto the stack. The argument must either be an object implementing __aexit__, or else a callback with the same signature as __aexit__ (use stack.push() to register a synchronous CM or exit callback)
- push_async_callback(callback): push the given callback onto the stack. The callback should return an awaitable (use stack.callback() to register synchronous callbacks)
|
msg310705 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2018-01-25 20:51 |
New changeset 1aa094f74039cd20fdc7df56c68f6848c18ce4dd by Yury Selivanov (Ilya Kulakov) in branch 'master':
bpo-29302: Implement contextlib.AsyncExitStack. (#4790)
https://github.com/python/cpython/commit/1aa094f74039cd20fdc7df56c68f6848c18ce4dd
|
msg310706 - (view) |
Author: Yury Selivanov (yselivanov) * |
Date: 2018-01-25 20:52 |
Thank you Alexander and Ilya!
|
msg356510 - (view) |
Author: Benjamin Peterson (benjamin.peterson) * |
Date: 2019-11-13 02:33 |
New changeset d6d6e2aa0249bb661541705335ddbb97a53d64c8 by Benjamin Peterson (Ilya Kulakov) in branch 'master':
Add Ilya Kulakov to Misc/ACKS. (GH-17130)
https://github.com/python/cpython/commit/d6d6e2aa0249bb661541705335ddbb97a53d64c8
|
msg356512 - (view) |
Author: miss-islington (miss-islington) |
Date: 2019-11-13 02:40 |
New changeset e5125f7b3b88d8d4814ed7bddbd4f34d24d763dd by Miss Islington (bot) in branch '3.8':
Add Ilya Kulakov to Misc/ACKS. (GH-17130)
https://github.com/python/cpython/commit/e5125f7b3b88d8d4814ed7bddbd4f34d24d763dd
|
|
Date |
User |
Action |
Args |
2022-04-11 14:58:42 | admin | set | github: 73488 |
2019-11-13 02:40:34 | miss-islington | set | nosy:
+ miss-islington messages:
+ msg356512
|
2019-11-13 02:33:34 | benjamin.peterson | set | nosy:
+ benjamin.peterson messages:
+ msg356510
|
2019-11-13 02:33:17 | miss-islington | set | pull_requests:
+ pull_request16649 |
2019-11-12 18:52:16 | Ilya.Kulakov | set | pull_requests:
+ pull_request16640 |
2018-01-25 20:52:36 | yselivanov | set | status: open -> closed resolution: fixed messages:
+ msg310706
stage: patch review -> resolved |
2018-01-25 20:51:26 | yselivanov | set | messages:
+ msg310705 |
2018-01-20 15:43:42 | ncoghlan | set | messages:
+ msg310353 |
2017-12-11 14:48:58 | cheryl.sabella | set | messages:
+ msg308046 |
2017-12-11 06:00:17 | Ilya.Kulakov | set | messages:
+ msg308014 |
2017-12-11 05:50:27 | Ilya.Kulakov | set | keywords:
+ patch stage: needs patch -> patch review pull_requests:
+ pull_request4690 |
2017-12-10 18:55:22 | cheryl.sabella | set | stage: needs patch |
2017-12-10 18:54:34 | cheryl.sabella | set | nosy:
+ cheryl.sabella messages:
+ msg307970
|
2017-11-23 21:30:50 | privatwolke | set | nosy:
+ privatwolke
|
2017-09-05 07:38:11 | njs | set | nosy:
+ njs
|
2017-08-30 00:05:04 | thehesiod | set | messages:
+ msg301004 |
2017-08-20 04:07:37 | ncoghlan | set | messages:
+ msg300595 |
2017-08-19 17:04:50 | yselivanov | set | messages:
+ msg300589 |
2017-08-19 17:02:00 | Ilya.Kulakov | set | messages:
+ msg300588 |
2017-08-19 16:44:25 | yselivanov | set | messages:
+ msg300586 |
2017-08-19 08:15:23 | Ilya.Kulakov | set | nosy:
+ Ilya.Kulakov messages:
+ msg300572
|
2017-05-05 19:40:11 | yselivanov | set | messages:
+ msg293138 versions:
- Python 3.6 |
2017-03-28 01:42:32 | thehesiod | set | messages:
+ msg290674 |
2017-03-01 20:41:21 | vstinner | set | nosy:
- vstinner
|
2017-03-01 17:42:03 | gvanrossum | set | nosy:
- gvanrossum
|
2017-03-01 16:06:16 | yselivanov | set | messages:
+ msg288762 |
2017-03-01 15:45:26 | yselivanov | set | messages:
+ msg288761 |
2017-03-01 08:37:34 | thehesiod | set | messages:
+ msg288744 |
2017-03-01 05:01:55 | ncoghlan | set | messages:
+ msg288735 |
2017-03-01 04:31:59 | ncoghlan | set | nosy:
+ gvanrossum, ncoghlan, vstinner, giampaolo.rodola, yselivanov
|
2017-01-28 14:16:22 | veky | set | nosy:
+ veky messages:
+ msg286411
|
2017-01-27 18:07:53 | thehesiod | set | messages:
+ msg286380 |
2017-01-27 18:07:04 | thehesiod | set | files:
- exit_stack.py |
2017-01-17 22:49:42 | thehesiod | create | |