diff -r 3eec7bcc14a4 Doc/library/contextlib.rst --- a/Doc/library/contextlib.rst Thu Mar 24 17:53:20 2016 +0100 +++ b/Doc/library/contextlib.rst Thu Mar 24 12:01:03 2016 -0700 @@ -18,6 +18,17 @@ 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 diff -r 3eec7bcc14a4 Doc/library/typing.rst --- a/Doc/library/typing.rst Thu Mar 24 17:53:20 2016 +0100 +++ b/Doc/library/typing.rst Thu Mar 24 12:01:03 2016 -0700 @@ -345,11 +345,11 @@ .. 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:: SupportsInt @@ -449,6 +449,10 @@ A generic version of :class:`collections.abc.ValuesView`. +.. class:: ContextManager(Generic[T_co]) + + A generic version of :class:`contextlib.AbstractContextManager`. + .. class:: Dict(dict, MutableMapping[KT, VT]) A generic version of :class:`dict`. diff -r 3eec7bcc14a4 Lib/contextlib.py --- a/Lib/contextlib.py Thu Mar 24 17:53:20 2016 +0100 +++ b/Lib/contextlib.py Thu Mar 24 12:01:03 2016 -0700 @@ -1,11 +1,35 @@ """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__ = ["AbstractContextManager", "contextmanager", "closing", + "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 +55,7 @@ return inner -class _GeneratorContextManager(ContextDecorator): +class _GeneratorContextManager(ContextDecorator, AbstractContextManager): """Helper for @contextmanager decorator.""" def __init__(self, func, args, kwds): @@ -134,7 +158,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 +183,7 @@ self.thing.close() -class _RedirectStream: +class _RedirectStream(AbstractContextManager): _stream = None @@ -199,7 +223,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 +254,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 +333,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 -r 3eec7bcc14a4 Lib/test/test_contextlib.py --- a/Lib/test/test_contextlib.py Thu Mar 24 17:53:20 2016 +0100 +++ b/Lib/test/test_contextlib.py Thu Mar 24 12:01:03 2016 -0700 @@ -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 -r 3eec7bcc14a4 Lib/test/test_typing.py --- a/Lib/test/test_typing.py Thu Mar 24 17:53:20 2016 +0100 +++ b/Lib/test/test_typing.py Thu Mar 24 12:01:03 2016 -0700 @@ -1,4 +1,5 @@ import asyncio +import contextlib import pickle import re import sys @@ -1208,6 +1209,19 @@ assert len(MMB[KT, VT]()) == 0 +class OtherABCTests(TestCase): + + 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): diff -r 3eec7bcc14a4 Lib/typing.py --- a/Lib/typing.py Thu Mar 24 17:53:20 2016 +0100 +++ b/Lib/typing.py Thu Mar 24 12:01:03 2016 -0700 @@ -5,6 +5,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 @@ -47,6 +48,9 @@ 'Sized', 'ValuesView', + # ABCs (NOT from collections.abc). + 'ContextManager', + # Structural checks, a.k.a. protocols. 'Reversible', 'SupportsAbs', @@ -1446,6 +1450,10 @@ pass +class ContextManager(Generic[T_co], extra=contextlib.AbstractContextManager): + __slots__ = () + + class Dict(dict, MutableMapping[KT, VT]): def __new__(cls, *args, **kwds):