summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJelle Zijlstra <jelle.zijlstra@gmail.com>2017-12-14 01:19:17 (GMT)
committerYury Selivanov <yury@magic.io>2017-12-14 01:19:17 (GMT)
commit176baa326be4ec2dc70ca0c054b7e2ab7ca6a9cf (patch)
tree308b48c9671eb33abe108d59b87eaaccad40d9eb
parentbfbf04ef18c93ca8cab0453f76aeea1d8fc23fb1 (diff)
downloadcpython-176baa326be4ec2dc70ca0c054b7e2ab7ca6a9cf.zip
cpython-176baa326be4ec2dc70ca0c054b7e2ab7ca6a9cf.tar.gz
cpython-176baa326be4ec2dc70ca0c054b7e2ab7ca6a9cf.tar.bz2
bpo-30241: implement contextlib.AbstractAsyncContextManager (#1412)
-rw-r--r--Doc/library/contextlib.rst11
-rw-r--r--Doc/whatsnew/3.7.rst5
-rw-r--r--Lib/contextlib.py27
-rw-r--r--Lib/test/test_contextlib_async.py49
-rw-r--r--Misc/NEWS.d/next/Library/2017-10-10-18-56-46.bpo-30241.F_go20.rst1
5 files changed, 88 insertions, 5 deletions
diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst
index 48ca0da..faa6c8a 100644
--- a/Doc/library/contextlib.rst
+++ b/Doc/library/contextlib.rst
@@ -29,6 +29,17 @@ Functions and classes provided:
.. versionadded:: 3.6
+.. class:: AbstractAsyncContextManager
+
+ 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
+
.. decorator:: contextmanager
diff --git a/Doc/whatsnew/3.7.rst b/Doc/whatsnew/3.7.rst
index 81a88a0..80f2a7f 100644
--- a/Doc/whatsnew/3.7.rst
+++ b/Doc/whatsnew/3.7.rst
@@ -306,8 +306,9 @@ is a list of strings, not bytes.
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`.)
cProfile
--------
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index c1f8a84..96c8c22 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -6,7 +6,8 @@ from collections import deque
from functools import wraps
__all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext",
- "AbstractContextManager", "ContextDecorator", "ExitStack",
+ "AbstractContextManager", "AbstractAsyncContextManager",
+ "ContextDecorator", "ExitStack",
"redirect_stdout", "redirect_stderr", "suppress"]
@@ -30,6 +31,27 @@ class AbstractContextManager(abc.ABC):
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:
+ return _collections_abc._check_methods(C, "__aenter__",
+ "__aexit__")
+ return NotImplemented
+
+
class ContextDecorator(object):
"A base class or mixin that enables context managers to work as decorators."
@@ -136,7 +158,8 @@ class _GeneratorContextManager(_GeneratorContextManagerBase,
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 42cc331..447ca96 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,53 @@ def _async_test(func):
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)
+
+ async with manager as context:
+ self.assertIs(manager, context)
+
+ 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 NoneAenter(ManagerFromScratch):
+ __aenter__ = None
+
+ self.assertFalse(issubclass(NoneAenter, AbstractAsyncContextManager))
+
+ class NoneAexit(ManagerFromScratch):
+ __aexit__ = None
+
+ self.assertFalse(issubclass(NoneAexit, AbstractAsyncContextManager))
+
+
class AsyncContextManagerTestCase(unittest.TestCase):
@_async_test
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 0000000..9b6c6f6
--- /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.