cpython: 841a263c0c56 (original) (raw)
Mercurial > cpython
changeset 100872:841a263c0c56
Issue #25609: Introduce contextlib.AbstractContextManager and typing.ContextManager. [#25609]
Brett Cannon brett@python.org | |
---|---|
date | Fri, 08 Apr 2016 12:15:27 -0700 |
parents | 778ccbe3cf74 |
children | 8e8a86f3b236 |
files | Doc/library/contextlib.rst Doc/library/typing.rst Doc/whatsnew/3.6.rst Lib/contextlib.py Lib/test/test_contextlib.py Lib/test/test_typing.py Lib/typing.py Misc/NEWS |
diffstat | 8 files changed, 138 insertions(+), 19 deletions(-)[+] [-] 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 |
line wrap: on
line diff
--- a/Doc/library/contextlib.rst +++ b/Doc/library/contextlib.rst @@ -18,6 +18,18 @@ Utilities 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 @@ Here's an example of doing this for a co
acquisition and release functions, along with an optional validation function,
and maps them to the context management protocol::
- class ResourceManager(AbstractContextManager): def init(self, acquire_resource, release_resource, check_resource_ok=None): self.acquire_resource = acquire_resource
--- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -345,15 +345,15 @@ The module defines the following classes .. class:: Iterable(Generic[T_co])
.. class:: Iterator(Iterable[T_co])
.. class:: Reversible(Iterable[T_co])
.. class:: SupportsInt
@@ -448,6 +448,12 @@ The module defines the following classes
A generic version of :class:collections.abc.ValuesView
.
+.. class:: ContextManager(Generic[T_co])
+
.. class:: Dict(dict, MutableMapping[KT, VT])
A generic version of :class:dict
.
--- a/Doc/whatsnew/3.6.rst
+++ b/Doc/whatsnew/3.6.rst
@@ -190,6 +190,18 @@ New 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
--------
@@ -246,6 +258,14 @@ telnetlib
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 @@ become proper keywords in Python 3.7.
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
.
--- 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"][](#l4.12)
+all = ["contextmanager", "closing", "AbstractContextManager",
"ContextDecorator", "ExitStack", "redirect_stdout",[](#l4.14)
"redirect_stderr", "suppress"][](#l4.15)
+ + +class AbstractContextManager(abc.ABC): +
- def enter(self):
"""Return `self` upon entering the runtime context."""[](#l4.23)
return self[](#l4.24)
- @abc.abstractmethod
- def exit(self, exc_type, exc_value, traceback):
"""Raise any exception triggered within the runtime context."""[](#l4.28)
return None[](#l4.29)
- @classmethod
- def subclasshook(cls, C):
if cls is AbstractContextManager:[](#l4.33)
if (any("__enter__" in B.__dict__ for B in C.__mro__) and[](#l4.34)
any("__exit__" in B.__dict__ for B in C.__mro__)):[](#l4.35)
return True[](#l4.36)
return NotImplemented[](#l4.37)
class ContextDecorator(object): @@ -31,7 +54,7 @@ class ContextDecorator(object): return inner -class _GeneratorContextManager(ContextDecorator): +class _GeneratorContextManager(ContextDecorator, AbstractContextManager): """Helper for @contextmanager decorator.""" def init(self, func, args, kwds): @@ -134,7 +157,7 @@ def contextmanager(func): 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 @@ class closing(object): self.thing.close() -class _RedirectStream: +class _RedirectStream(AbstractContextManager): _stream = None @@ -199,7 +222,7 @@ class redirect_stderr(_RedirectStream): _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 @@ class suppress:
Inspired by discussions on http://bugs.python.org/issue13585[](#l4.80)
-class ExitStack(object): +class ExitStack(AbstractContextManager): """Context manager for dynamic management of a stack of exit callbacks For example: @@ -309,9 +332,6 @@ class ExitStack(object): """Immediately unwind the context stack""" self.exit(None, None, None)
- def exit(self, *exc_details): received_exc = exc_details[0] is not None
--- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -12,6 +12,39 @@ except ImportError: threading = None +class TestAbstractContextManager(unittest.TestCase): +
- def test_enter(self):
class DefaultEnter(AbstractContextManager):[](#l5.10)
def __exit__(self, *args):[](#l5.11)
super().__exit__(*args)[](#l5.12)
manager = DefaultEnter()[](#l5.14)
self.assertIs(manager.__enter__(), manager)[](#l5.15)
- def test_exit_is_abstract(self):
class MissingExit(AbstractContextManager):[](#l5.18)
pass[](#l5.19)
with self.assertRaises(TypeError):[](#l5.21)
MissingExit()[](#l5.22)
- def test_structural_subclassing(self):
class ManagerFromScratch:[](#l5.25)
def __enter__(self):[](#l5.26)
return self[](#l5.27)
def __exit__(self, exc_type, exc_value, traceback):[](#l5.28)
return None[](#l5.29)
self.assertTrue(issubclass(ManagerFromScratch, AbstractContextManager))[](#l5.31)
class DefaultEnter(AbstractContextManager):[](#l5.33)
def __exit__(self, *args):[](#l5.34)
super().__exit__(*args)[](#l5.35)
self.assertTrue(issubclass(DefaultEnter, AbstractContextManager))[](#l5.37)
+ + class ContextManagerTestCase(unittest.TestCase): def test_contextmanager_plain(self):
--- 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 @@ class CollectionsAbcTests(TestCase): assert len(MMBKT, VT) == 0 +class OtherABCTests(TestCase): +
- @skipUnless(hasattr(typing, 'ContextManager'),
'requires typing.ContextManager')[](#l6.15)
- def test_contextmanager(self):
@contextlib.contextmanager[](#l6.17)
def manager():[](#l6.18)
yield 42[](#l6.19)
cm = manager()[](#l6.21)
assert isinstance(cm, typing.ContextManager)[](#l6.22)
assert isinstance(cm, typing.ContextManager[int])[](#l6.23)
assert not isinstance(42, typing.ContextManager)[](#l6.24)
+ + class NamedTupleTests(TestCase): def test_basics(self): @@ -1447,6 +1463,8 @@ class AllTests(TestCase): assert 'ValuesView' in a assert 'cast' in a assert 'overload' in a
if hasattr(contextlib, 'AbstractContextManager'):[](#l6.34)
assert 'ContextManager' in a[](#l6.35) # Check that io and re are not exported.[](#l6.36) assert 'io' not in a[](#l6.37) assert 're' not in a[](#l6.38)
--- 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 @@ class ValuesView(MappingView[VT_co], ext pass +if hasattr(contextlib, 'AbstractContextManager'):
- class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager):
__slots__ = ()[](#l7.17)
- all.append('ContextManager')
+ + class Dict(dict, MutableMapping[KT, VT]): def new(cls, *args, **kwds):
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -237,6 +237,9 @@ Core and Builtins Library ------- +- Issue #25609: Introduce contextlib.AbstractContextManager and