Skip to content

Commit 29fda8d

Browse files
ilevkivskyigvanrossum
authored andcommitted
bpo-28556: Updates to typing module (#2076)
This PR contains two updates to typing module: - Support ContextManager on all versions (original PR by Jelle Zijlstra). - Add generic AsyncContextManager.
1 parent ca81615 commit 29fda8d

File tree

3 files changed

+99
-5
lines changed

3 files changed

+99
-5
lines changed

Lib/test/test_typing.py

+36-3
Original file line numberDiff line numberDiff line change
@@ -1552,6 +1552,12 @@ def __anext__(self) -> T_a:
15521552
return data
15531553
else:
15541554
raise StopAsyncIteration
1555+
1556+
class ACM:
1557+
async def __aenter__(self) -> int:
1558+
return 42
1559+
async def __aexit__(self, etype, eval, tb):
1560+
return None
15551561
"""
15561562

15571563
if ASYNCIO:
@@ -1562,12 +1568,13 @@ def __anext__(self) -> T_a:
15621568
else:
15631569
# fake names for the sake of static analysis
15641570
asyncio = None
1565-
AwaitableWrapper = AsyncIteratorWrapper = object
1571+
AwaitableWrapper = AsyncIteratorWrapper = ACM = object
15661572

15671573
PY36 = sys.version_info[:2] >= (3, 6)
15681574

15691575
PY36_TESTS = """
15701576
from test import ann_module, ann_module2, ann_module3
1577+
from typing import AsyncContextManager
15711578
15721579
class A:
15731580
y: float
@@ -1604,6 +1611,16 @@ def __str__(self):
16041611
return f'{self.x} -> {self.y}'
16051612
def __add__(self, other):
16061613
return 0
1614+
1615+
async def g_with(am: AsyncContextManager[int]):
1616+
x: int
1617+
async with am as x:
1618+
return x
1619+
1620+
try:
1621+
g_with(ACM()).send(None)
1622+
except StopIteration as e:
1623+
assert e.args[0] == 42
16071624
"""
16081625

16091626
if PY36:
@@ -2156,8 +2173,6 @@ class B: ...
21562173

21572174
class OtherABCTests(BaseTestCase):
21582175

2159-
@skipUnless(hasattr(typing, 'ContextManager'),
2160-
'requires typing.ContextManager')
21612176
def test_contextmanager(self):
21622177
@contextlib.contextmanager
21632178
def manager():
@@ -2167,6 +2182,24 @@ def manager():
21672182
self.assertIsInstance(cm, typing.ContextManager)
21682183
self.assertNotIsInstance(42, typing.ContextManager)
21692184

2185+
@skipUnless(ASYNCIO, 'Python 3.5 required')
2186+
def test_async_contextmanager(self):
2187+
class NotACM:
2188+
pass
2189+
self.assertIsInstance(ACM(), typing.AsyncContextManager)
2190+
self.assertNotIsInstance(NotACM(), typing.AsyncContextManager)
2191+
@contextlib.contextmanager
2192+
def manager():
2193+
yield 42
2194+
2195+
cm = manager()
2196+
self.assertNotIsInstance(cm, typing.AsyncContextManager)
2197+
self.assertEqual(typing.AsyncContextManager[int].__args__, (int,))
2198+
with self.assertRaises(TypeError):
2199+
isinstance(42, typing.AsyncContextManager[int])
2200+
with self.assertRaises(TypeError):
2201+
typing.AsyncContextManager[int, str]
2202+
21702203

21712204
class TypeTests(BaseTestCase):
21722205

Lib/typing.py

+59-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import collections.abc as collections_abc
1111
except ImportError:
1212
import collections as collections_abc # Fallback for PY3.2.
13+
if sys.version_info[:2] >= (3, 6):
14+
import _collections_abc # Needed for private function _check_methods # noqa
1315
try:
1416
from types import WrapperDescriptorType, MethodWrapperType, MethodDescriptorType
1517
except ImportError:
@@ -37,6 +39,7 @@
3739
# for 'Generic' and ABCs below.
3840
'ByteString',
3941
'Container',
42+
'ContextManager',
4043
'Hashable',
4144
'ItemsView',
4245
'Iterable',
@@ -57,8 +60,8 @@
5760
# AsyncIterable,
5861
# Coroutine,
5962
# Collection,
60-
# ContextManager,
6163
# AsyncGenerator,
64+
# AsyncContextManager
6265

6366
# Structural checks, a.k.a. protocols.
6467
'Reversible',
@@ -1949,7 +1952,61 @@ class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView):
19491952
if hasattr(contextlib, 'AbstractContextManager'):
19501953
class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
19511954
__slots__ = ()
1952-
__all__.append('ContextManager')
1955+
else:
1956+
class ContextManager(Generic[T_co]):
1957+
__slots__ = ()
1958+
1959+
def __enter__(self):
1960+
return self
1961+
1962+
@abc.abstractmethod
1963+
def __exit__(self, exc_type, exc_value, traceback):
1964+
return None
1965+
1966+
@classmethod
1967+
def __subclasshook__(cls, C):
1968+
if cls is ContextManager:
1969+
# In Python 3.6+, it is possible to set a method to None to
1970+
# explicitly indicate that the class does not implement an ABC
1971+
# (https://bugs.python.org/issue25958), but we do not support
1972+
# that pattern here because this fallback class is only used
1973+
# in Python 3.5 and earlier.
1974+
if (any("__enter__" in B.__dict__ for B in C.__mro__) and
1975+
any("__exit__" in B.__dict__ for B in C.__mro__)):
1976+
return True
1977+
return NotImplemented
1978+
1979+
1980+
if hasattr(contextlib, 'AbstractAsyncContextManager'):
1981+
class AsyncContextManager(Generic[T_co],
1982+
extra=contextlib.AbstractAsyncContextManager):
1983+
__slots__ = ()
1984+
1985+
__all__.append('AsyncContextManager')
1986+
elif sys.version_info[:2] >= (3, 5):
1987+
exec("""
1988+
class AsyncContextManager(Generic[T_co]):
1989+
__slots__ = ()
1990+
1991+
async def __aenter__(self):
1992+
return self
1993+
1994+
@abc.abstractmethod
1995+
async def __aexit__(self, exc_type, exc_value, traceback):
1996+
return None
1997+
1998+
@classmethod
1999+
def __subclasshook__(cls, C):
2000+
if cls is AsyncContextManager:
2001+
if sys.version_info[:2] >= (3, 6):
2002+
return _collections_abc._check_methods(C, "__aenter__", "__aexit__")
2003+
if (any("__aenter__" in B.__dict__ for B in C.__mro__) and
2004+
any("__aexit__" in B.__dict__ for B in C.__mro__)):
2005+
return True
2006+
return NotImplemented
2007+
2008+
__all__.append('AsyncContextManager')
2009+
""")
19532010

19542011

19552012
class Dict(dict, MutableMapping[KT, VT], extra=dict):

Misc/NEWS

+4
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ Library
354354
non-blocking mode if it succeeded to aquire the lock but the acquire took
355355
longer than the timeout.
356356

357+
- bpo-28556: Updates to typing module: Add generic AsyncContextManager, add
358+
support for ContextManager on all versions. Original PRs by Jelle Zijlstra
359+
and Ivan Levkivskyi
360+
357361
- bpo-30605: re.compile() no longer raises a BytesWarning when compiling a
358362
bytes instance with misplaced inline modifier. Patch by Roy Williams.
359363

0 commit comments

Comments
 (0)