[Python-checkins] cpython: Issue #25609: Introduce contextlib.AbstractContextManager and
brett.cannon
python-checkins at python.org
Fri Apr 8 15:16:27 EDT 2016
https://hg.python.org/cpython/rev/841a263c0c56
changeset: 100872:841a263c0c56
user: Brett Cannon <brett at python.org>
date: Fri Apr 08 12:15:27 2016 -0700
summary:
Issue #25609: Introduce contextlib.AbstractContextManager and
typing.ContextManager.
files:
Doc/library/contextlib.rst | 16 ++++++++-
Doc/library/typing.rst | 12 +++++-
Doc/whatsnew/3.6.rst | 26 +++++++++++++-
Lib/contextlib.py | 42 ++++++++++++++++++------
Lib/test/test_contextlib.py | 33 +++++++++++++++++++
Lib/test/test_typing.py | 18 ++++++++++
Lib/typing.py | 7 ++++
Misc/NEWS | 3 +
8 files changed, 138 insertions(+), 19 deletions(-)
diff --git a/Doc/library/contextlib.rst b/Doc/library/contextlib.rst
--- a/Doc/library/contextlib.rst
+++ b/Doc/library/contextlib.rst
@@ -18,6 +18,18 @@
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
This function is a :term:`decorator` that can be used to define a factory
@@ -447,9 +459,9 @@
acquisition and release functions, along with an optional validation function,
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):
self.acquire_resource = acquire_resource
diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst
--- a/Doc/library/typing.rst
+++ b/Doc/library/typing.rst
@@ -345,15 +345,15 @@
.. 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])
- A generic version of the :class:`collections.abc.Iterator`.
+ A generic version of :class:`collections.abc.Iterator`.
.. class:: Reversible(Iterable[T_co])
- A generic version of the :class:`collections.abc.Reversible`.
+ A generic version of :class:`collections.abc.Reversible`.
.. class:: SupportsInt
@@ -448,6 +448,12 @@
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])
A generic version of :class:`dict`.
diff --git a/Doc/whatsnew/3.6.rst b/Doc/whatsnew/3.6.rst
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -190,6 +190,18 @@
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
--------
@@ -246,6 +258,14 @@
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
-------------
@@ -372,9 +392,9 @@
Deprecated Python modules, functions and methods
------------------------------------------------
-* :meth:`importlib.machinery.SourceFileLoader` and
- :meth:`importlib.machinery.SourcelessFileLoader` are now deprecated. They
- were the only remaining implementations of
+* :meth:`importlib.machinery.SourceFileLoader.load_module` and
+ :meth:`importlib.machinery.SourcelessFileLoader.load_module` are now
+ deprecated. They were the only remaining implementations of
:meth:`importlib.abc.Loader.load_module` in :mod:`importlib` that had not
been deprecated in previous versions of Python in favour of
:meth:`importlib.abc.Loader.exec_module`.
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -1,11 +1,34 @@
"""Utilities for with-statement contexts. See PEP 343."""
-
+import abc
import sys
from collections import deque
from functools import wraps
-__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",
- "redirect_stdout", "redirect_stderr", "suppress"]
+__all__ = ["contextmanager", "closing", "AbstractContextManager",
+ "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):
@@ -31,7 +54,7 @@
return inner
-class _GeneratorContextManager(ContextDecorator):
+class _GeneratorContextManager(ContextDecorator, AbstractContextManager):
"""Helper for @contextmanager decorator."""
def __init__(self, func, args, kwds):
@@ -134,7 +157,7 @@
return helper
-class closing(object):
+class closing(AbstractContextManager):
"""Context to automatically close something at the end of a block.
Code like this:
@@ -159,7 +182,7 @@
self.thing.close()
-class _RedirectStream:
+class _RedirectStream(AbstractContextManager):
_stream = None
@@ -199,7 +222,7 @@
_stream = "stderr"
-class suppress:
+class suppress(AbstractContextManager):
"""Context manager to suppress specified exceptions
After the exception is suppressed, execution proceeds with the next
@@ -230,7 +253,7 @@
# 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
For example:
@@ -309,9 +332,6 @@
"""Immediately unwind the context stack"""
self.__exit__(None, None, None)
- def __enter__(self):
- return self
-
def __exit__(self, *exc_details):
received_exc = exc_details[0] is not None
diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py
--- a/Lib/test/test_contextlib.py
+++ b/Lib/test/test_contextlib.py
@@ -12,6 +12,39 @@
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):
def test_contextmanager_plain(self):
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -1,3 +1,4 @@
+import contextlib
import pickle
import re
import sys
@@ -1309,6 +1310,21 @@
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):
def test_basics(self):
@@ -1447,6 +1463,8 @@
assert 'ValuesView' in a
assert 'cast' in a
assert 'overload' in a
+ if hasattr(contextlib, 'AbstractContextManager'):
+ assert 'ContextManager' in a
# Check that io and re are not exported.
assert 'io' not in a
assert 're' not in a
diff --git a/Lib/typing.py b/Lib/typing.py
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1,6 +1,7 @@
import abc
from abc import abstractmethod, abstractproperty
import collections
+import contextlib
import functools
import re as stdlib_re # Avoid confusion with the re we export.
import sys
@@ -1530,6 +1531,12 @@
pass
+if hasattr(contextlib, 'AbstractContextManager'):
+ class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
+ __slots__ = ()
+ __all__.append('ContextManager')
+
+
class Dict(dict, MutableMapping[KT, VT]):
def __new__(cls, *args, **kwds):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -237,6 +237,9 @@
Library
-------
+- Issue #25609: Introduce contextlib.AbstractContextManager and
+ typing.ContextManager.
+
- Issue #26709: Fixed Y2038 problem in loading binary PLists.
- Issue #23735: Handle terminal resizing with Readline 6.3+ by installing our
--
Repository URL: https://hg.python.org/cpython
More information about the Python-checkins
mailing list