--- ../cpython/Lib/contextlib.py 2021-06-26 16:28:03.835372955 +1000 +++ contextlib2.py 2021-06-26 17:40:30.047079570 +1000 @@ -1,19 +1,32 @@ -"""Utilities for with-statement contexts. See PEP 343.""" +"""contextlib2 - backports and enhancements to the contextlib module""" + import abc import sys +import warnings import _collections_abc from collections import deque from functools import wraps -from types import MethodType, GenericAlias +from types import MethodType + +# Python 3.6/3.7/3.8 compatibility: GenericAlias may not be defined +try: + from types import GenericAlias +except ImportError: + # If the real GenericAlias type doesn't exist, __class_getitem__ won't be used, + # so the fallback placeholder doesn't need to provide any meaningful behaviour + class GenericAlias: + pass + __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext", "AbstractContextManager", "AbstractAsyncContextManager", "AsyncExitStack", "ContextDecorator", "ExitStack", "redirect_stdout", "redirect_stderr", "suppress", "aclosing"] +# Backwards compatibility +__all__ += ["ContextStack"] class AbstractContextManager(abc.ABC): - """An abstract base class for context managers.""" __class_getitem__ = classmethod(GenericAlias) @@ -60,6 +73,23 @@ class ContextDecorator(object): "A base class or mixin that enables context managers to work as decorators." + def refresh_cm(self): + """Returns the context manager used to actually wrap the call to the + decorated function. + + The default implementation just returns *self*. + + Overriding this method allows otherwise one-shot context managers + like _GeneratorContextManager to support use as decorators via + implicit recreation. + + DEPRECATED: refresh_cm was never added to the standard library's + ContextDecorator API + """ + warnings.warn("refresh_cm was never added to the standard library", + DeprecationWarning) + return self._recreate_cm() + def _recreate_cm(self): """Return a recreated instance of self. @@ -430,7 +460,9 @@ return MethodType(cm_exit, cm) @staticmethod - def _create_cb_wrapper(callback, /, *args, **kwds): + def _create_cb_wrapper(*args, **kwds): + # Python 3.6/3.7 compatibility: no native positional-only args syntax + callback, *args = args def _exit_wrapper(exc_type, exc, tb): callback(*args, **kwds) return _exit_wrapper @@ -479,11 +511,18 @@ self._push_cm_exit(cm, _exit) return result - def callback(self, callback, /, *args, **kwds): + def callback(*args, **kwds): """Registers an arbitrary callback and arguments. Cannot suppress exceptions. """ + # Python 3.6/3.7 compatibility: no native positional-only args syntax + try: + self, callback, *args = args + except ValueError as exc: + exc_details = str(exc).partition("(")[2] + msg = "Not enough positional arguments {}".format(exc_details) + raise TypeError(msg) from None _exit_wrapper = self._create_cb_wrapper(callback, *args, **kwds) # We changed the signature, so using @wraps is not appropriate, but @@ -589,7 +628,9 @@ return MethodType(cm_exit, cm) @staticmethod - def _create_async_cb_wrapper(callback, /, *args, **kwds): + def _create_async_cb_wrapper(*args, **kwds): + # Python 3.6/3.7 compatibility: no native positional-only args syntax + callback, *args = args async def _exit_wrapper(exc_type, exc, tb): await callback(*args, **kwds) return _exit_wrapper @@ -624,11 +665,18 @@ self._push_async_cm_exit(exit, exit_method) return exit # Allow use as a decorator - def push_async_callback(self, callback, /, *args, **kwds): + def push_async_callback(*args, **kwds): """Registers an arbitrary coroutine function and arguments. Cannot suppress exceptions. """ + # Python 3.6/3.7 compatibility: no native positional-only args syntax + try: + self, callback, *args = args + except ValueError as exc: + exc_details = str(exc).partition("(")[2] + msg = "Not enough positional arguments {}".format(exc_details) + raise TypeError(msg) from None _exit_wrapper = self._create_async_cb_wrapper(callback, *args, **kwds) # We changed the signature, so using @wraps is not appropriate, but @@ -729,3 +777,22 @@ async def __aexit__(self, *excinfo): pass + + +# Preserve backwards compatibility +class ContextStack(ExitStack): + """Backwards compatibility alias for ExitStack""" + + def __init__(self): + warnings.warn("ContextStack has been renamed to ExitStack", + DeprecationWarning) + super(ContextStack, self).__init__() + + def register_exit(self, callback): + return self.push(callback) + + def register(self, callback, *args, **kwds): + return self.callback(callback, *args, **kwds) + + def preserve(self): + return self.pop_all()