Commit 9e080e0e authored by Brett Cannon's avatar Brett Cannon

Issue #25609: Introduce contextlib.AbstractContextManager and

typing.ContextManager.
parent c5b5ba9b
...@@ -18,6 +18,18 @@ Utilities ...@@ -18,6 +18,18 @@ Utilities
Functions and classes provided: Functions and classes provided:
.. class:: AbstractContextManager
An abstract base class for classes that implement
:meth:`object.__enter__` and :meth:`object.__exit__`. A default
implementation for :meth:`object.__enter__` is provided which returns
``self`` while :meth:`object.__exit__` is an abstract method which by default
returns ``None``. See also the definition of :ref:`typecontextmanager`.
.. versionadded:: 3.6
.. decorator:: contextmanager .. decorator:: contextmanager
This function is a :term:`decorator` that can be used to define a factory This function is a :term:`decorator` that can be used to define a factory
...@@ -447,9 +459,9 @@ Here's an example of doing this for a context manager that accepts resource ...@@ -447,9 +459,9 @@ Here's an example of doing this for a context manager that accepts resource
acquisition and release functions, along with an optional validation function, acquisition and release functions, along with an optional validation function,
and maps them to the context management protocol:: and maps them to the context management protocol::
from contextlib import contextmanager, ExitStack from contextlib import contextmanager, AbstractContextManager, ExitStack
class ResourceManager: class ResourceManager(AbstractContextManager):
def __init__(self, acquire_resource, release_resource, check_resource_ok=None): def __init__(self, acquire_resource, release_resource, check_resource_ok=None):
self.acquire_resource = acquire_resource self.acquire_resource = acquire_resource
......
...@@ -345,15 +345,15 @@ The module defines the following classes, functions and decorators: ...@@ -345,15 +345,15 @@ The module defines the following classes, functions and decorators:
.. class:: Iterable(Generic[T_co]) .. class:: Iterable(Generic[T_co])
A generic version of the :class:`collections.abc.Iterable`. A generic version of :class:`collections.abc.Iterable`.
.. class:: Iterator(Iterable[T_co]) .. class:: Iterator(Iterable[T_co])
A generic version of the :class:`collections.abc.Iterator`. A generic version of :class:`collections.abc.Iterator`.
.. class:: Reversible(Iterable[T_co]) .. class:: Reversible(Iterable[T_co])
A generic version of the :class:`collections.abc.Reversible`. A generic version of :class:`collections.abc.Reversible`.
.. class:: SupportsInt .. class:: SupportsInt
...@@ -448,6 +448,12 @@ The module defines the following classes, functions and decorators: ...@@ -448,6 +448,12 @@ The module defines the following classes, functions and decorators:
A generic version of :class:`collections.abc.ValuesView`. A generic version of :class:`collections.abc.ValuesView`.
.. class:: ContextManager(Generic[T_co])
A generic version of :class:`contextlib.AbstractContextManager`.
.. versionadded:: 3.6
.. class:: Dict(dict, MutableMapping[KT, VT]) .. class:: Dict(dict, MutableMapping[KT, VT])
A generic version of :class:`dict`. A generic version of :class:`dict`.
......
...@@ -190,6 +190,18 @@ New Modules ...@@ -190,6 +190,18 @@ New Modules
Improved Modules Improved Modules
================ ================
contextlib
----------
The :class:`contextlib.AbstractContextManager` class has been added to
provide an abstract base class for context managers. It provides a
sensible default implementation for `__enter__()` which returns
`self` and leaves `__exit__()` an abstract method. A matching
class has been added to the :mod:`typing` module as
:class:`typing.ContextManager`.
(Contributed by Brett Cannon in :issue:`25609`.)
datetime datetime
-------- --------
...@@ -246,6 +258,14 @@ telnetlib ...@@ -246,6 +258,14 @@ telnetlib
Stéphane Wirtel in :issue:`25485`). Stéphane Wirtel in :issue:`25485`).
typing
------
The :class:`typing.ContextManager` class has been added for
representing :class:`contextlib.AbstractContextManager`.
(Contributed by Brett Cannon in :issue:`25609`.)
unittest.mock unittest.mock
------------- -------------
...@@ -372,9 +392,9 @@ become proper keywords in Python 3.7. ...@@ -372,9 +392,9 @@ become proper keywords in Python 3.7.
Deprecated Python modules, functions and methods Deprecated Python modules, functions and methods
------------------------------------------------ ------------------------------------------------
* :meth:`importlib.machinery.SourceFileLoader` and * :meth:`importlib.machinery.SourceFileLoader.load_module` and
:meth:`importlib.machinery.SourcelessFileLoader` are now deprecated. They :meth:`importlib.machinery.SourcelessFileLoader.load_module` are now
were the only remaining implementations of deprecated. They were the only remaining implementations of
:meth:`importlib.abc.Loader.load_module` in :mod:`importlib` that had not :meth:`importlib.abc.Loader.load_module` in :mod:`importlib` that had not
been deprecated in previous versions of Python in favour of been deprecated in previous versions of Python in favour of
:meth:`importlib.abc.Loader.exec_module`. :meth:`importlib.abc.Loader.exec_module`.
......
"""Utilities for with-statement contexts. See PEP 343.""" """Utilities for with-statement contexts. See PEP 343."""
import abc
import sys import sys
from collections import deque from collections import deque
from functools import wraps from functools import wraps
__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack", __all__ = ["contextmanager", "closing", "AbstractContextManager",
"redirect_stdout", "redirect_stderr", "suppress"] "ContextDecorator", "ExitStack", "redirect_stdout",
"redirect_stderr", "suppress"]
class AbstractContextManager(abc.ABC):
"""An abstract base class for context managers."""
def __enter__(self):
"""Return `self` upon entering the runtime context."""
return self
@abc.abstractmethod
def __exit__(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""
return None
@classmethod
def __subclasshook__(cls, C):
if cls is AbstractContextManager:
if (any("__enter__" in B.__dict__ for B in C.__mro__) and
any("__exit__" in B.__dict__ for B in C.__mro__)):
return True
return NotImplemented
class ContextDecorator(object): class ContextDecorator(object):
...@@ -31,7 +54,7 @@ class ContextDecorator(object): ...@@ -31,7 +54,7 @@ class ContextDecorator(object):
return inner return inner
class _GeneratorContextManager(ContextDecorator): class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
"""Helper for @contextmanager decorator.""" """Helper for @contextmanager decorator."""
def __init__(self, func, args, kwds): def __init__(self, func, args, kwds):
...@@ -134,7 +157,7 @@ def contextmanager(func): ...@@ -134,7 +157,7 @@ def contextmanager(func):
return helper return helper
class closing(object): class closing(AbstractContextManager):
"""Context to automatically close something at the end of a block. """Context to automatically close something at the end of a block.
Code like this: Code like this:
...@@ -159,7 +182,7 @@ class closing(object): ...@@ -159,7 +182,7 @@ class closing(object):
self.thing.close() self.thing.close()
class _RedirectStream: class _RedirectStream(AbstractContextManager):
_stream = None _stream = None
...@@ -199,7 +222,7 @@ class redirect_stderr(_RedirectStream): ...@@ -199,7 +222,7 @@ class redirect_stderr(_RedirectStream):
_stream = "stderr" _stream = "stderr"
class suppress: class suppress(AbstractContextManager):
"""Context manager to suppress specified exceptions """Context manager to suppress specified exceptions
After the exception is suppressed, execution proceeds with the next After the exception is suppressed, execution proceeds with the next
...@@ -230,7 +253,7 @@ class suppress: ...@@ -230,7 +253,7 @@ class suppress:
# Inspired by discussions on http://bugs.python.org/issue13585 # Inspired by discussions on http://bugs.python.org/issue13585
class ExitStack(object): class ExitStack(AbstractContextManager):
"""Context manager for dynamic management of a stack of exit callbacks """Context manager for dynamic management of a stack of exit callbacks
For example: For example:
...@@ -309,9 +332,6 @@ class ExitStack(object): ...@@ -309,9 +332,6 @@ class ExitStack(object):
"""Immediately unwind the context stack""" """Immediately unwind the context stack"""
self.__exit__(None, None, None) self.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, *exc_details): def __exit__(self, *exc_details):
received_exc = exc_details[0] is not None received_exc = exc_details[0] is not None
......
...@@ -12,6 +12,39 @@ except ImportError: ...@@ -12,6 +12,39 @@ except ImportError:
threading = None threading = None
class TestAbstractContextManager(unittest.TestCase):
def test_enter(self):
class DefaultEnter(AbstractContextManager):
def __exit__(self, *args):
super().__exit__(*args)
manager = DefaultEnter()
self.assertIs(manager.__enter__(), manager)
def test_exit_is_abstract(self):
class MissingExit(AbstractContextManager):
pass
with self.assertRaises(TypeError):
MissingExit()
def test_structural_subclassing(self):
class ManagerFromScratch:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
return None
self.assertTrue(issubclass(ManagerFromScratch, AbstractContextManager))
class DefaultEnter(AbstractContextManager):
def __exit__(self, *args):
super().__exit__(*args)
self.assertTrue(issubclass(DefaultEnter, AbstractContextManager))
class ContextManagerTestCase(unittest.TestCase): class ContextManagerTestCase(unittest.TestCase):
def test_contextmanager_plain(self): def test_contextmanager_plain(self):
......
import contextlib
import pickle import pickle
import re import re
import sys import sys
...@@ -1309,6 +1310,21 @@ class CollectionsAbcTests(TestCase): ...@@ -1309,6 +1310,21 @@ class CollectionsAbcTests(TestCase):
assert len(MMB[KT, VT]()) == 0 assert len(MMB[KT, VT]()) == 0
class OtherABCTests(TestCase):
@skipUnless(hasattr(typing, 'ContextManager'),
'requires typing.ContextManager')
def test_contextmanager(self):
@contextlib.contextmanager
def manager():
yield 42
cm = manager()
assert isinstance(cm, typing.ContextManager)
assert isinstance(cm, typing.ContextManager[int])
assert not isinstance(42, typing.ContextManager)
class NamedTupleTests(TestCase): class NamedTupleTests(TestCase):
def test_basics(self): def test_basics(self):
...@@ -1447,6 +1463,8 @@ class AllTests(TestCase): ...@@ -1447,6 +1463,8 @@ class AllTests(TestCase):
assert 'ValuesView' in a assert 'ValuesView' in a
assert 'cast' in a assert 'cast' in a
assert 'overload' in a assert 'overload' in a
if hasattr(contextlib, 'AbstractContextManager'):
assert 'ContextManager' in a
# Check that io and re are not exported. # Check that io and re are not exported.
assert 'io' not in a assert 'io' not in a
assert 're' not in a assert 're' not in a
......
import abc import abc
from abc import abstractmethod, abstractproperty from abc import abstractmethod, abstractproperty
import collections import collections
import contextlib
import functools import functools
import re as stdlib_re # Avoid confusion with the re we export. import re as stdlib_re # Avoid confusion with the re we export.
import sys import sys
...@@ -1530,6 +1531,12 @@ class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView): ...@@ -1530,6 +1531,12 @@ class ValuesView(MappingView[VT_co], extra=collections_abc.ValuesView):
pass pass
if hasattr(contextlib, 'AbstractContextManager'):
class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
__slots__ = ()
__all__.append('ContextManager')
class Dict(dict, MutableMapping[KT, VT]): class Dict(dict, MutableMapping[KT, VT]):
def __new__(cls, *args, **kwds): def __new__(cls, *args, **kwds):
......
...@@ -237,6 +237,9 @@ Core and Builtins ...@@ -237,6 +237,9 @@ Core and Builtins
Library Library
------- -------
- Issue #25609: Introduce contextlib.AbstractContextManager and
typing.ContextManager.
- Issue #26709: Fixed Y2038 problem in loading binary PLists. - Issue #26709: Fixed Y2038 problem in loading binary PLists.
- Issue #23735: Handle terminal resizing with Readline 6.3+ by installing our - Issue #23735: Handle terminal resizing with Readline 6.3+ by installing our
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment