Skip to content

Commit b0f92e5

Browse files
committed
implement contextlib.AbstractAsyncContextManager
1 parent 13f1f42 commit b0f92e5

File tree

4 files changed

+73
-5
lines changed

4 files changed

+73
-5
lines changed

Doc/library/contextlib.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ Functions and classes provided:
2929
.. versionadded:: 3.6
3030

3131

32+
.. class:: AbstractAsyncContextManager
33+
34+
An :term:`abstract base class` similar to
35+
:class:`~contextlib.AbstractContextManager`, but for
36+
:ref:`asynchronous context managers <async-context-managers>`, which
37+
implement :meth:`object.__aenter__` and :meth:`object.__aexit__`.
38+
39+
.. versionadded:: 3.7
40+
3241

3342
.. decorator:: contextmanager
3443

Doc/whatsnew/3.7.rst

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@ instead of spaces. (Contributed by Xiang Zhang in :issue:`30103`.)
105105
contextlib
106106
----------
107107

108-
:func:`contextlib.asynccontextmanager` has been added. (Contributed by
109-
Jelle Zijlstra in :issue:`29679`.)
108+
:func:`~contextlib.asynccontextmanager` and
109+
:class:`~contextlib.AbstractAsyncContextManager` have been added. (Contributed
110+
by Jelle Zijlstra in :issue:`29679` and :issue:`30241`.)
110111

111112
distutils
112113
---------

Lib/contextlib.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
from functools import wraps
66

77
__all__ = ["asynccontextmanager", "contextmanager", "closing",
8-
"AbstractContextManager", "ContextDecorator", "ExitStack",
8+
"AbstractContextManager", "AbstractAsyncContextManager",
9+
"ContextDecorator", "ExitStack",
910
"redirect_stdout", "redirect_stderr", "suppress"]
1011

1112

@@ -31,6 +32,28 @@ def __subclasshook__(cls, C):
3132
return NotImplemented
3233

3334

35+
class AbstractAsyncContextManager(abc.ABC):
36+
37+
"""An abstract base class for asynchronous context managers."""
38+
39+
async def __aenter__(self):
40+
"""Return `self` upon entering the runtime context."""
41+
return self
42+
43+
@abc.abstractmethod
44+
async def __aexit__(self, exc_type, exc_value, traceback):
45+
"""Raise any exception triggered within the runtime context."""
46+
return None
47+
48+
@classmethod
49+
def __subclasshook__(cls, C):
50+
if cls is AbstractAsyncContextManager:
51+
if (any("__aenter__" in B.__dict__ for B in C.__mro__) and
52+
any("__aexit__" in B.__dict__ for B in C.__mro__)):
53+
return True
54+
return NotImplemented
55+
56+
3457
class ContextDecorator(object):
3558
"A base class or mixin that enables context managers to work as decorators."
3659

@@ -137,7 +160,8 @@ def __exit__(self, type, value, traceback):
137160
raise RuntimeError("generator didn't stop after throw()")
138161

139162

140-
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase):
163+
class _AsyncGeneratorContextManager(_GeneratorContextManagerBase,
164+
AbstractAsyncContextManager):
141165
"""Helper for @asynccontextmanager."""
142166

143167
async def __aenter__(self):

Lib/test/test_contextlib_async.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import asyncio
2-
from contextlib import asynccontextmanager
2+
from contextlib import asynccontextmanager, AbstractAsyncContextManager
33
import functools
44
from test import support
55
import unittest
@@ -20,6 +20,40 @@ def wrapper(*args, **kwargs):
2020
return wrapper
2121

2222

23+
class TestAbstractAsyncContextManager(unittest.TestCase):
24+
25+
@_async_test
26+
async def test_enter(self):
27+
class DefaultEnter(AbstractAsyncContextManager):
28+
async def __aexit__(self, *args):
29+
await super().__aexit__(*args)
30+
31+
manager = DefaultEnter()
32+
self.assertIs(await manager.__aenter__(), manager)
33+
34+
def test_exit_is_abstract(self):
35+
class MissingAexit(AbstractAsyncContextManager):
36+
pass
37+
38+
with self.assertRaises(TypeError):
39+
MissingAexit()
40+
41+
def test_structural_subclassing(self):
42+
class ManagerFromScratch:
43+
async def __aenter__(self):
44+
return self
45+
async def __aexit__(self, exc_type, exc_value, traceback):
46+
return None
47+
48+
self.assertTrue(issubclass(ManagerFromScratch, AbstractAsyncContextManager))
49+
50+
class DefaultEnter(AbstractAsyncContextManager):
51+
async def __aexit__(self, *args):
52+
await super().__aexit__(*args)
53+
54+
self.assertTrue(issubclass(DefaultEnter, AbstractAsyncContextManager))
55+
56+
2357
class AsyncContextManagerTestCase(unittest.TestCase):
2458

2559
@_async_test

0 commit comments

Comments
 (0)