From db99d4a562bacab013c664e5419eccc5020fe40a Mon Sep 17 00:00:00 2001 From: Heckad Date: Fri, 29 May 2020 17:55:05 +0300 Subject: [PATCH 01/12] Add basic AsyncContextDecorator implementation --- Lib/contextlib.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index ff92d9f913f4c2..c0d6519e580435 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -80,6 +80,22 @@ def inner(*args, **kwds): return inner +class AsyncContextDecorator(object): + "A base class or mixin that enables async context managers to work as decorators." + + def __recreate_cm(self): + """Return a recreated instance of self. + """ + return self + + def __call__(self, func): + @wraps(func) + def inner(*args, **kwds): + async with self._recreate_cm(): + return await func(*args, **kwds) + return inner + + class _GeneratorContextManagerBase: """Shared functionality for @contextmanager and @asynccontextmanager.""" @@ -167,9 +183,16 @@ def __exit__(self, type, value, traceback): class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, - AbstractAsyncContextManager): + AbstractAsyncContextManager, + AsyncContextDecorator): """Helper for @asynccontextmanager.""" + def _recreate_cm(self): + # _AGCM instances are one-shot context managers, so the + # ACM must be recreated each time a decorated function is + # called + return self.__class__(self.func, self.args, self.kwds) + async def __aenter__(self): try: return await self.gen.__anext__() From 7d1243a3ba75104f0f72215003027672199d348b Mon Sep 17 00:00:00 2001 From: Heckad Date: Fri, 29 May 2020 18:19:04 +0300 Subject: [PATCH 02/12] Fix --- Lib/contextlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index c0d6519e580435..f51cdd85df50dd 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -90,7 +90,7 @@ def __recreate_cm(self): def __call__(self, func): @wraps(func) - def inner(*args, **kwds): + async def inner(*args, **kwds): async with self._recreate_cm(): return await func(*args, **kwds) return inner From 20aeef8a09afda0a1d1d25dbd1da5854fec321c4 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Fri, 29 May 2020 15:25:42 +0000 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst diff --git a/Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst b/Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst new file mode 100644 index 00000000000000..66b75779784655 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-05-29-15-25-41.bpo-40816.w61Pob.rst @@ -0,0 +1 @@ +Add AsyncContextDecorator to contextlib to support async context manager as a decorator. \ No newline at end of file From 858a98fbe9446e4ec781b7f3caaf90505ce2ca23 Mon Sep 17 00:00:00 2001 From: Heckad Date: Mon, 20 Jul 2020 12:11:26 +0300 Subject: [PATCH 04/12] Add test --- Lib/test/test_contextlib_async.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 43fb7fced1bfdb..096cbc15bbff22 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -278,6 +278,25 @@ async def woohoo(self, func, args, kwds): async with woohoo(self=11, func=22, args=33, kwds=44) as target: self.assertEqual(target, (11, 22, 33, 44)) + async def test_recursive(self): + depth = 0 + @asynccontextmanager + async def woohoo(): + nonlocal depth + before = depth + depth += 1 + yield + depth -= 1 + self.assertEqual(depth, before) + + @woohoo() + async def recursive(): + if depth < 10: + recursive() + + await recursive() + self.assertEqual(depth, 0) + class TestAsyncExitStack(TestBaseExitStack, unittest.TestCase): class SyncAsyncExitStack(AsyncExitStack): From 1922773f11080ac6fefeea4278e6f43536dad692 Mon Sep 17 00:00:00 2001 From: Heckad Date: Mon, 20 Jul 2020 19:57:39 +0300 Subject: [PATCH 05/12] Fix test --- Lib/test/test_contextlib_async.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 096cbc15bbff22..8bddc6dac71f00 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -278,6 +278,7 @@ async def woohoo(self, func, args, kwds): async with woohoo(self=11, func=22, args=33, kwds=44) as target: self.assertEqual(target, (11, 22, 33, 44)) + @_async_test async def test_recursive(self): depth = 0 @asynccontextmanager From dcaa6207b298adf5e2eb379384e7984bbcfe163a Mon Sep 17 00:00:00 2001 From: Heckad Date: Wed, 22 Jul 2020 11:02:38 +0300 Subject: [PATCH 06/12] Fix function name --- Lib/contextlib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index f51cdd85df50dd..6838bea96a96c6 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -83,7 +83,7 @@ def inner(*args, **kwds): class AsyncContextDecorator(object): "A base class or mixin that enables async context managers to work as decorators." - def __recreate_cm(self): + def _recreate_cm(self): """Return a recreated instance of self. """ return self From 5e6cec7394d964ea2cdf897146744c140a6873dd Mon Sep 17 00:00:00 2001 From: Heckad Date: Wed, 22 Jul 2020 11:52:08 +0300 Subject: [PATCH 07/12] Fix test --- Lib/test/test_contextlib_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 8bddc6dac71f00..e9cc2b62e11461 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -293,7 +293,7 @@ async def woohoo(): @woohoo() async def recursive(): if depth < 10: - recursive() + await recursive() await recursive() self.assertEqual(depth, 0) From a5fd9292023fb756d520db58993a648a51ce5cbe Mon Sep 17 00:00:00 2001 From: Heckad Date: Thu, 23 Jul 2020 12:05:36 +0300 Subject: [PATCH 08/12] Improve tests --- Lib/test/test_contextlib_async.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index e9cc2b62e11461..a631218e158389 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -281,8 +281,13 @@ async def woohoo(self, func, args, kwds): @_async_test async def test_recursive(self): depth = 0 + ncols = 0 + @asynccontextmanager async def woohoo(): + nonlocal ncols + ncols += 1 + nonlocal depth before = depth depth += 1 @@ -296,6 +301,8 @@ async def recursive(): await recursive() await recursive() + + self.assertEqual(ncols, 10) self.assertEqual(depth, 0) From a1735c5e316952e47e719e6e990fd5541dc3017a Mon Sep 17 00:00:00 2001 From: Heckad Date: Thu, 23 Jul 2020 21:17:23 +0300 Subject: [PATCH 09/12] Add doc --- Doc/library/contextlib.rst | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 0aa4ad76523480..6ae9cd2d5c3296 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -351,6 +351,43 @@ Functions and classes provided: .. versionadded:: 3.2 +.. class:: AsyncContextManager + + Similar as ContextManger only for async + + Example of ``ContextDecorator``:: + + from asyncio import run + from contextlib import AsyncContextDecorator + + class mycontext(AsyncContextDecorator): + async def __aenter__(self): + print('Starting') + return self + + async def __aexit__(self, *exc): + print('Finishing') + return False + + >>> @mycontext() + ... async def function(): + ... print('The bit in the middle') + ... + >>> run(function()) + Starting + The bit in the middle + Finishing + + >>> async def function(): + ... async with mycontext(): + ... print('The bit in the middle') + ... + >>> run(function()) + Starting + The bit in the middle + Finishing + + .. class:: ExitStack() A context manager that is designed to make it easy to programmatically From 81df65c31556f5ac39ba07223faeca31bce973fe Mon Sep 17 00:00:00 2001 From: Heckad Date: Sun, 26 Jul 2020 14:55:18 +0300 Subject: [PATCH 10/12] Improve doc --- Doc/library/contextlib.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 6ae9cd2d5c3296..4a0dbbf619e917 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -126,6 +126,16 @@ Functions and classes provided: .. versionadded:: 3.7 + :func:`asynccontextmanager` uses :class:`AsyncContextDecorator` so the context managers + it creates can be used as decorators as well as in :keyword:`async with` statements. + When used as a decorator, a new generator instance is implicitly created on + each function call (this allows the otherwise "one-shot" context managers + created by :func:`asynccontextmanager` to meet the requirement that context + managers support multiple invocations in order to be used as decorators). + + .. versionchanged:: 3.10 + Use of :class:`AsyncContextDecorator`. + .. function:: closing(thing) From 1293c87dedc11c85ba70c817b86c4be4318039e6 Mon Sep 17 00:00:00 2001 From: Kazantcev Andrey <45011689+heckad@users.noreply.github.com> Date: Sat, 31 Oct 2020 22:59:32 +0300 Subject: [PATCH 11/12] Update Doc/library/contextlib.rst Co-authored-by: Yury Selivanov --- Doc/library/contextlib.rst | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 4a0dbbf619e917..45f541fd04bc2b 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -126,15 +126,30 @@ Functions and classes provided: .. versionadded:: 3.7 - :func:`asynccontextmanager` uses :class:`AsyncContextDecorator` so the context managers - it creates can be used as decorators as well as in :keyword:`async with` statements. + Context managers defined with :func:`asynccontextmanager` can be used + either as decorators or with :keyword:`async with` statements:: + + import time + + async def timeit(): + now = time.monotonic() + try: + yield + finally: + print(f'it took {time.monotonic() - now}s to run') + + @timeit() + async def main(): + # ... async code ... + When used as a decorator, a new generator instance is implicitly created on - each function call (this allows the otherwise "one-shot" context managers + each function call. This allows the otherwise "one-shot" context managers created by :func:`asynccontextmanager` to meet the requirement that context - managers support multiple invocations in order to be used as decorators). + managers support multiple invocations in order to be used as decorators. .. versionchanged:: 3.10 - Use of :class:`AsyncContextDecorator`. + Async context managers created with :func:`asynccontextmanager` can + be used as decorators. .. function:: closing(thing) From 1bace398398b36ddc0be4dc063dcb27e0068e94e Mon Sep 17 00:00:00 2001 From: Heckad Date: Sun, 1 Nov 2020 00:27:47 +0300 Subject: [PATCH 12/12] Fix whitespaces --- Doc/library/contextlib.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 45f541fd04bc2b..6872de382e897c 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -128,16 +128,16 @@ Functions and classes provided: Context managers defined with :func:`asynccontextmanager` can be used either as decorators or with :keyword:`async with` statements:: - + import time - - async def timeit(): + + async def timeit(): now = time.monotonic() try: yield finally: print(f'it took {time.monotonic() - now}s to run') - + @timeit() async def main(): # ... async code ... @@ -377,11 +377,11 @@ Functions and classes provided: .. class:: AsyncContextManager - + Similar as ContextManger only for async Example of ``ContextDecorator``:: - + from asyncio import run from contextlib import AsyncContextDecorator pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy