From b0f92e538992e4f38b93fff9defe1bb78c23ece3 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 1 May 2017 08:59:47 -0700 Subject: [PATCH 1/4] implement contextlib.AbstractAsyncContextManager --- Doc/library/contextlib.rst | 9 ++++++++ Doc/whatsnew/3.7.rst | 5 +++-- Lib/contextlib.py | 28 ++++++++++++++++++++++-- Lib/test/test_contextlib_async.py | 36 ++++++++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index 19793693b7ba68..be0a480c5d1deb 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -29,6 +29,15 @@ Functions and classes provided: .. versionadded:: 3.6 +.. class:: AbstractAsyncContextManager + + An :term:`abstract base class` similar to + :class:`~contextlib.AbstractContextManager`, but for + :ref:`asynchronous context managers `, which + implement :meth:`object.__aenter__` and :meth:`object.__aexit__`. + + .. versionadded:: 3.7 + .. decorator:: contextmanager diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst index 7edf4fc3cf4269..b1802477dab970 100644 --- a/Doc/whatsnew/3.7.rst +++ b/Doc/whatsnew/3.7.rst @@ -105,8 +105,9 @@ instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.) contextlib ---------- -:func:`contextlib.asynccontextmanager` has been added. (Contributed by -Jelle Zijlstra in :issue:`29679`.) +:func:`~contextlib.asynccontextmanager` and +:class:`~contextlib.AbstractAsyncContextManager` have been added. (Contributed +by Jelle Zijlstra in :issue:`29679` and :issue:`30241`.) distutils --------- diff --git a/Lib/contextlib.py b/Lib/contextlib.py index c53b35e8d5adaa..f3cd4b2e518b03 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -5,7 +5,8 @@ from functools import wraps __all__ = ["asynccontextmanager", "contextmanager", "closing", - "AbstractContextManager", "ContextDecorator", "ExitStack", + "AbstractContextManager", "AbstractAsyncContextManager", + "ContextDecorator", "ExitStack", "redirect_stdout", "redirect_stderr", "suppress"] @@ -31,6 +32,28 @@ def __subclasshook__(cls, C): return NotImplemented +class AbstractAsyncContextManager(abc.ABC): + + """An abstract base class for asynchronous context managers.""" + + async def __aenter__(self): + """Return `self` upon entering the runtime context.""" + return self + + @abc.abstractmethod + async def __aexit__(self, exc_type, exc_value, traceback): + """Raise any exception triggered within the runtime context.""" + return None + + @classmethod + def __subclasshook__(cls, C): + if cls is AbstractAsyncContextManager: + if (any("__aenter__" in B.__dict__ for B in C.__mro__) and + any("__aexit__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + + class ContextDecorator(object): "A base class or mixin that enables context managers to work as decorators." @@ -137,7 +160,8 @@ def __exit__(self, type, value, traceback): raise RuntimeError("generator didn't stop after throw()") -class _AsyncGeneratorContextManager(_GeneratorContextManagerBase): +class _AsyncGeneratorContextManager(_GeneratorContextManagerBase, + AbstractAsyncContextManager): """Helper for @asynccontextmanager.""" async def __aenter__(self): diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 42cc331c0afdb9..2898ce98321c9e 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -1,5 +1,5 @@ import asyncio -from contextlib import asynccontextmanager +from contextlib import asynccontextmanager, AbstractAsyncContextManager import functools from test import support import unittest @@ -20,6 +20,40 @@ def wrapper(*args, **kwargs): return wrapper +class TestAbstractAsyncContextManager(unittest.TestCase): + + @_async_test + async def test_enter(self): + class DefaultEnter(AbstractAsyncContextManager): + async def __aexit__(self, *args): + await super().__aexit__(*args) + + manager = DefaultEnter() + self.assertIs(await manager.__aenter__(), manager) + + def test_exit_is_abstract(self): + class MissingAexit(AbstractAsyncContextManager): + pass + + with self.assertRaises(TypeError): + MissingAexit() + + def test_structural_subclassing(self): + class ManagerFromScratch: + async def __aenter__(self): + return self + async def __aexit__(self, exc_type, exc_value, traceback): + return None + + self.assertTrue(issubclass(ManagerFromScratch, AbstractAsyncContextManager)) + + class DefaultEnter(AbstractAsyncContextManager): + async def __aexit__(self, *args): + await super().__aexit__(*args) + + self.assertTrue(issubclass(DefaultEnter, AbstractAsyncContextManager)) + + class AsyncContextManagerTestCase(unittest.TestCase): @_async_test From 9fb10286b0707924ba69633812f79b0c04c5c6c1 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 3 May 2017 20:33:38 -0700 Subject: [PATCH 2/4] support the "= None" pattern --- Lib/contextlib.py | 6 +++--- Lib/test/test_contextlib_async.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index f3cd4b2e518b03..30a409cf8de6f0 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -1,6 +1,7 @@ """Utilities for with-statement contexts. See PEP 343.""" import abc import sys +import _collections_abc from collections import deque from functools import wraps @@ -48,9 +49,8 @@ async def __aexit__(self, exc_type, exc_value, traceback): @classmethod def __subclasshook__(cls, C): if cls is AbstractAsyncContextManager: - if (any("__aenter__" in B.__dict__ for B in C.__mro__) and - any("__aexit__" in B.__dict__ for B in C.__mro__)): - return True + return _collections_abc._check_methods(C, "__aenter__", + "__aexit__") return NotImplemented diff --git a/Lib/test/test_contextlib_async.py b/Lib/test/test_contextlib_async.py index 2898ce98321c9e..447ca9651222e3 100644 --- a/Lib/test/test_contextlib_async.py +++ b/Lib/test/test_contextlib_async.py @@ -31,6 +31,9 @@ async def __aexit__(self, *args): manager = DefaultEnter() self.assertIs(await manager.__aenter__(), manager) + async with manager as context: + self.assertIs(manager, context) + def test_exit_is_abstract(self): class MissingAexit(AbstractAsyncContextManager): pass @@ -53,6 +56,16 @@ async def __aexit__(self, *args): self.assertTrue(issubclass(DefaultEnter, AbstractAsyncContextManager)) + class NoneAenter(ManagerFromScratch): + __aenter__ = None + + self.assertFalse(issubclass(NoneAenter, AbstractAsyncContextManager)) + + class NoneAexit(ManagerFromScratch): + __aexit__ = None + + self.assertFalse(issubclass(NoneAexit, AbstractAsyncContextManager)) + class AsyncContextManagerTestCase(unittest.TestCase): From 290802ffb8cacea0f654ab31e84a43198a3f452f Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 10 Oct 2017 18:56:51 -0700 Subject: [PATCH 3/4] add NEWS entry --- .../NEWS.d/next/Library/2017-10-10-18-56-46.bpo-30241.F_go20.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2017-10-10-18-56-46.bpo-30241.F_go20.rst diff --git a/Misc/NEWS.d/next/Library/2017-10-10-18-56-46.bpo-30241.F_go20.rst b/Misc/NEWS.d/next/Library/2017-10-10-18-56-46.bpo-30241.F_go20.rst new file mode 100644 index 00000000000000..9b6c6f6e422cc1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-10-10-18-56-46.bpo-30241.F_go20.rst @@ -0,0 +1 @@ +Add contextlib.AbstractAsyncContextManager. Patch by Jelle Zijlstra. From 171c597add243223bfb5fdeda8521c82469c8317 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 13 Dec 2017 16:47:40 -0800 Subject: [PATCH 4/4] rewrite docs as suggested by @1st1 --- Doc/library/contextlib.rst | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst index be0a480c5d1deb..cc987fb357e2ae 100644 --- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -31,10 +31,12 @@ Functions and classes provided: .. class:: AbstractAsyncContextManager - An :term:`abstract base class` similar to - :class:`~contextlib.AbstractContextManager`, but for - :ref:`asynchronous context managers `, which - implement :meth:`object.__aenter__` and :meth:`object.__aexit__`. + An :term:`abstract base class` for classes that implement + :meth:`object.__aenter__` and :meth:`object.__aexit__`. A default + implementation for :meth:`object.__aenter__` is provided which returns + ``self`` while :meth:`object.__aexit__` is an abstract method which by default + returns ``None``. See also the definition of + :ref:`async-context-managers`. .. versionadded:: 3.7 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