diff --git a/dev/py3_12_py_to_contextlib2.patch b/dev/py3_12_py_to_contextlib2.patch index 69ef466..a73b309 100644 --- a/dev/py3_12_py_to_contextlib2.patch +++ b/dev/py3_12_py_to_contextlib2.patch @@ -1,5 +1,5 @@ --- /home/ncoghlan/devel/contextlib2/../cpython/Lib/contextlib.py 2024-05-23 11:57:09.210023505 +1000 -+++ /home/ncoghlan/devel/contextlib2/contextlib2/__init__.py 2024-05-23 16:31:24.317343460 +1000 ++++ /home/ncoghlan/devel/contextlib2/contextlib2/__init__.py 2024-05-23 17:05:06.549142813 +1000 @@ -5,7 +5,46 @@ import _collections_abc from collections import deque @@ -48,7 +48,7 @@ __all__ = ["asynccontextmanager", "contextmanager", "closing", "nullcontext", "AbstractContextManager", "AbstractAsyncContextManager", -@@ -62,6 +101,23 @@ +@@ -62,6 +101,24 @@ class ContextDecorator(object): "A base class or mixin that enables context managers to work as decorators." @@ -65,6 +65,7 @@ + DEPRECATED: refresh_cm was never added to the standard library's + ContextDecorator API + """ ++ import warnings # Only import if needed for the deprecation warning + warnings.warn("refresh_cm was never added to the standard library", + DeprecationWarning) + return self._recreate_cm() @@ -72,7 +73,7 @@ def _recreate_cm(self): """Return a recreated instance of self. -@@ -520,7 +576,7 @@ +@@ -520,7 +577,7 @@ try: _enter = cls.__enter__ _exit = cls.__exit__ @@ -81,7 +82,7 @@ raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does " f"not support the context manager protocol") from None result = _enter(cm) -@@ -652,7 +708,7 @@ +@@ -652,7 +709,7 @@ try: _enter = cls.__aenter__ _exit = cls.__aexit__ @@ -90,14 +91,14 @@ raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does " f"not support the asynchronous context manager protocol" ) from None -@@ -798,3 +854,22 @@ +@@ -798,3 +855,22 @@ def __exit__(self, *excinfo): os.chdir(self._old_cwd.pop()) + +# Preserve backwards compatibility +class ContextStack(ExitStack): -+ """Backwards compatibility alias for ExitStack""" ++ """(DEPRECATED) Backwards compatibility alias for ExitStack""" + + def __init__(self): + import warnings # Only import if needed for the deprecation warning diff --git a/dev/py3_12_rst_to_contextlib2.patch b/dev/py3_12_rst_to_contextlib2.patch index 50b8f80..5b5c017 100644 --- a/dev/py3_12_rst_to_contextlib2.patch +++ b/dev/py3_12_rst_to_contextlib2.patch @@ -1,5 +1,5 @@ --- /home/ncoghlan/devel/contextlib2/../cpython/Doc/library/contextlib.rst 2024-05-20 12:53:59.936907756 +1000 -+++ /home/ncoghlan/devel/contextlib2/docs/contextlib2.rst 2024-05-23 16:52:15.696031500 +1000 ++++ /home/ncoghlan/devel/contextlib2/docs/contextlib2.rst 2024-05-23 17:39:52.671083724 +1000 @@ -1,20 +1,5 @@ -:mod:`!contextlib` --- Utilities for :keyword:`!with`\ -statement contexts -========================================================================== @@ -45,7 +45,22 @@ .. decorator:: contextmanager -@@ -95,9 +80,6 @@ +@@ -49,12 +34,12 @@ + + While many objects natively support use in with statements, sometimes a + resource needs to be managed that isn't a context manager in its own right, +- and doesn't implement a ``close()`` method for use with ``contextlib.closing`` ++ and doesn't implement a ``close()`` method for use with ``contextlib2.closing`` + + An abstract example would be the following to ensure correct resource + management:: + +- from contextlib import contextmanager ++ from contextlib2 import contextmanager + + @contextmanager + def managed_resource(*args, **kwds): +@@ -95,13 +80,10 @@ created by :func:`contextmanager` to meet the requirement that context managers support multiple invocations in order to be used as decorators). @@ -55,7 +70,21 @@ .. decorator:: asynccontextmanager -@@ -126,7 +108,10 @@ +- Similar to :func:`~contextlib.contextmanager`, but creates an ++ Similar to :func:`~contextlib2.contextmanager`, but creates an + :ref:`asynchronous context manager `. + + This function is a :term:`decorator` that can be used to define a factory +@@ -112,7 +94,7 @@ + + A simple example:: + +- from contextlib import asynccontextmanager ++ from contextlib2 import asynccontextmanager + + @asynccontextmanager + async def get_connection(): +@@ -126,13 +108,16 @@ async with get_connection() as conn: return conn.query('SELECT ...') @@ -67,7 +96,14 @@ Context managers defined with :func:`asynccontextmanager` can be used either as decorators or with :keyword:`async with` statements:: -@@ -151,10 +136,6 @@ + + import time +- from contextlib import asynccontextmanager ++ from contextlib2 import asynccontextmanager + + @asynccontextmanager + async def timeit(): +@@ -151,17 +136,13 @@ created by :func:`asynccontextmanager` to meet the requirement that context managers support multiple invocations in order to be used as decorators. @@ -78,6 +114,41 @@ .. function:: closing(thing) + Return a context manager that closes *thing* upon completion of the block. This + is basically equivalent to:: + +- from contextlib import contextmanager ++ from contextlib2 import contextmanager + + @contextmanager + def closing(thing): +@@ -172,7 +153,7 @@ + + And lets you write code like this:: + +- from contextlib import closing ++ from contextlib2 import closing + from urllib.request import urlopen + + with closing(urlopen('https://www.python.org')) as page: +@@ -196,7 +177,7 @@ + Return an async context manager that calls the ``aclose()`` method of *thing* + upon completion of the block. This is basically equivalent to:: + +- from contextlib import asynccontextmanager ++ from contextlib2 import asynccontextmanager + + @asynccontextmanager + async def aclosing(thing): +@@ -209,7 +190,7 @@ + generators when they happen to exit early by :keyword:`break` or an + exception. For example:: + +- from contextlib import aclosing ++ from contextlib2 import aclosing + + async with aclosing(my_generator()) as values: + async for value in values: @@ -221,7 +202,8 @@ variables work as expected, and the exit code isn't run after the lifetime of some task it depends on). @@ -88,6 +159,19 @@ .. _simplifying-support-for-single-optional-context-managers: +@@ -235,10 +217,10 @@ + def myfunction(arg, ignore_exceptions=False): + if ignore_exceptions: + # Use suppress to ignore all exceptions. +- cm = contextlib.suppress(Exception) ++ cm = contextlib2.suppress(Exception) + else: + # Do not ignore any exceptions, cm has no effect. +- cm = contextlib.nullcontext() ++ cm = contextlib2.nullcontext() + with cm: + # Do something + @@ -269,11 +251,11 @@ async with cm as session: # Send http requests with session @@ -104,6 +188,15 @@ .. function:: suppress(*exceptions) +@@ -290,7 +272,7 @@ + + For example:: + +- from contextlib import suppress ++ from contextlib2 import suppress + + with suppress(FileNotFoundError): + os.remove('somefile.tmp') @@ -314,13 +296,15 @@ If the code within the :keyword:`!with` block raises a @@ -125,7 +218,7 @@ .. function:: redirect_stdout(new_target) -@@ -359,7 +343,8 @@ +@@ -359,17 +343,19 @@ This context manager is :ref:`reentrant `. @@ -135,7 +228,10 @@ .. function:: redirect_stderr(new_target) -@@ -369,7 +354,8 @@ + +- Similar to :func:`~contextlib.redirect_stdout` but redirecting ++ Similar to :func:`~contextlib2.redirect_stdout` but redirecting + :data:`sys.stderr` to another file or file-like object. This context manager is :ref:`reentrant `. @@ -155,6 +251,24 @@ .. class:: ContextDecorator() +@@ -402,7 +389,7 @@ + + Example of ``ContextDecorator``:: + +- from contextlib import ContextDecorator ++ from contextlib2 import ContextDecorator + + class mycontext(ContextDecorator): + def __enter__(self): +@@ -449,7 +436,7 @@ + Existing context managers that already have a base class can be extended by + using ``ContextDecorator`` as a mixin class:: + +- from contextlib import ContextDecorator ++ from contextlib2 import ContextDecorator + + class mycontext(ContextBaseClass, ContextDecorator): + def __enter__(self): @@ -464,8 +451,6 @@ statements. If this is not the case, then the original construct with the explicit :keyword:`!with` statement inside the function should be used. @@ -164,6 +278,15 @@ .. class:: AsyncContextDecorator +@@ -474,7 +459,7 @@ + Example of ``AsyncContextDecorator``:: + + from asyncio import run +- from contextlib import AsyncContextDecorator ++ from contextlib2 import AsyncContextDecorator + + class mycontext(AsyncContextDecorator): + async def __aenter__(self): @@ -505,7 +490,8 @@ The bit in the middle Finishing @@ -198,7 +321,14 @@ .. method:: push(exit) -@@ -632,9 +620,10 @@ +@@ -627,14 +615,16 @@ + The :meth:`~ExitStack.close` method is not implemented; :meth:`aclose` must be used + instead. + +- .. coroutinemethod:: enter_async_context(cm) ++ .. method:: enter_async_context(cm) ++ :async: + Similar to :meth:`ExitStack.enter_context` but expects an asynchronous context manager. @@ -212,7 +342,17 @@ .. method:: push_async_exit(exit) -@@ -658,7 +647,9 @@ +@@ -645,7 +635,8 @@ + + Similar to :meth:`ExitStack.callback` but expects a coroutine function. + +- .. coroutinemethod:: aclose() ++ .. method:: aclose() ++ :async: + + Similar to :meth:`ExitStack.close` but properly handles awaitables. + +@@ -658,13 +649,15 @@ # the async with statement, even if attempts to open a connection # later in the list raise an exception. @@ -223,3 +363,91 @@ Examples and Recipes -------------------- + + This section describes some examples and recipes for making effective use of +-the tools provided by :mod:`contextlib`. ++the tools provided by :mod:`contextlib2`. + + + Supporting a variable number of context managers +@@ -728,7 +721,7 @@ + acquisition and release functions, along with an optional validation function, + and maps them to the context management protocol:: + +- from contextlib import contextmanager, AbstractContextManager, ExitStack ++ from contextlib2 import contextmanager, AbstractContextManager, ExitStack + + class ResourceManager(AbstractContextManager): + +@@ -788,7 +781,7 @@ + execution at the end of a ``with`` statement, and then later decide to skip + executing that callback:: + +- from contextlib import ExitStack ++ from contextlib2 import ExitStack + + with ExitStack() as stack: + stack.callback(cleanup_resources) +@@ -802,7 +795,7 @@ + If a particular application uses this pattern a lot, it can be simplified + even further by means of a small helper class:: + +- from contextlib import ExitStack ++ from contextlib2 import ExitStack + + class Callback(ExitStack): + def __init__(self, callback, /, *args, **kwds): +@@ -822,7 +815,7 @@ + :meth:`ExitStack.callback` to declare the resource cleanup in + advance:: + +- from contextlib import ExitStack ++ from contextlib2 import ExitStack + + with ExitStack() as stack: + @stack.callback +@@ -849,7 +842,7 @@ + inheriting from :class:`ContextDecorator` provides both capabilities in a + single definition:: + +- from contextlib import ContextDecorator ++ from contextlib2 import ContextDecorator + import logging + + logging.basicConfig(level=logging.INFO) +@@ -911,7 +904,7 @@ + context managers, and will complain about the underlying generator failing + to yield if an attempt is made to use them a second time:: + +- >>> from contextlib import contextmanager ++ >>> from contextlib2 import contextmanager + >>> @contextmanager + ... def singleuse(): + ... print("Before") +@@ -946,7 +939,7 @@ + :func:`suppress`, :func:`redirect_stdout`, and :func:`chdir`. Here's a very + simple example of reentrant use:: + +- >>> from contextlib import redirect_stdout ++ >>> from contextlib2 import redirect_stdout + >>> from io import StringIO + >>> stream = StringIO() + >>> write_to_stream = redirect_stdout(stream) +@@ -992,7 +985,7 @@ + when leaving any with statement, regardless of where those callbacks + were added:: + +- >>> from contextlib import ExitStack ++ >>> from contextlib2 import ExitStack + >>> stack = ExitStack() + >>> with stack: + ... stack.callback(print, "Callback: from first context") +@@ -1026,7 +1019,7 @@ + Using separate :class:`ExitStack` instances instead of reusing a single + instance avoids that problem:: + +- >>> from contextlib import ExitStack ++ >>> from contextlib2 import ExitStack + >>> with ExitStack() as outer_stack: + ... outer_stack.callback(print, "Callback: from outer context") + ... with ExitStack() as inner_stack: diff --git a/dev/py3_12_test_async_to_contextlib2.patch b/dev/py3_12_test_async_to_contextlib2.patch index 271f7d2..a2cbaa6 100644 --- a/dev/py3_12_test_async_to_contextlib2.patch +++ b/dev/py3_12_test_async_to_contextlib2.patch @@ -1,13 +1,15 @@ --- /home/ncoghlan/devel/contextlib2/../cpython/Lib/test/test_contextlib_async.py 2024-05-23 11:57:09.276022441 +1000 -+++ /home/ncoghlan/devel/contextlib2/test/test_contextlib_async.py 2024-05-23 16:17:45.220019904 +1000 -@@ -1,5 +1,5 @@ ++++ /home/ncoghlan/devel/contextlib2/test/test_contextlib_async.py 2024-05-23 17:39:05.799797895 +1000 +@@ -1,5 +1,7 @@ ++"""Unit tests for asynchronous features of contextlib2.py""" ++ import asyncio -from contextlib import ( +from contextlib2 import ( asynccontextmanager, AbstractAsyncContextManager, AsyncExitStack, nullcontext, aclosing, contextmanager) import functools -@@ -7,7 +7,7 @@ +@@ -7,7 +9,7 @@ import unittest import traceback @@ -16,7 +18,7 @@ support.requires_working_socket(module=True) -@@ -202,7 +202,8 @@ +@@ -202,7 +204,8 @@ await ctx.__aexit__(TypeError, TypeError('foo'), None) if support.check_impl_detail(cpython=True): # The "gen" attribute is an implementation detail. @@ -26,7 +28,7 @@ @_async_test async def test_contextmanager_trap_no_yield(self): -@@ -226,7 +227,8 @@ +@@ -226,7 +229,8 @@ await ctx.__aexit__(None, None, None) if support.check_impl_detail(cpython=True): # The "gen" attribute is an implementation detail. @@ -36,7 +38,7 @@ @_async_test async def test_contextmanager_non_normalised(self): -@@ -669,12 +671,13 @@ +@@ -669,12 +673,13 @@ async def __aenter__(self): pass @@ -53,7 +55,7 @@ await stack.enter_async_context(LacksExit()) self.assertFalse(stack._exit_callbacks) -@@ -752,7 +755,8 @@ +@@ -752,7 +757,8 @@ cm.__aenter__ = object() cm.__aexit__ = object() stack = self.exit_stack() diff --git a/dev/py3_12_test_to_contextlib2.patch b/dev/py3_12_test_to_contextlib2.patch index c6f2d98..275a1b4 100644 --- a/dev/py3_12_test_to_contextlib2.patch +++ b/dev/py3_12_test_to_contextlib2.patch @@ -1,5 +1,11 @@ --- /home/ncoghlan/devel/contextlib2/../cpython/Lib/test/test_contextlib.py 2024-05-23 11:57:09.276022441 +1000 -+++ /home/ncoghlan/devel/contextlib2/test/test_contextlib.py 2024-05-23 16:17:54.963869109 +1000 ++++ /home/ncoghlan/devel/contextlib2/test/test_contextlib.py 2024-05-23 17:38:37.295232213 +1000 +@@ -1,4 +1,4 @@ +-"""Unit tests for contextlib.py, and other context managers.""" ++"""Unit tests for synchronous features of contextlib2.py""" + + import io + import os @@ -7,7 +7,7 @@ import threading import traceback diff --git a/docs/contextlib2.rst b/docs/contextlib2.rst index 0a8fbb3..2e6def0 100644 --- a/docs/contextlib2.rst +++ b/docs/contextlib2.rst @@ -34,12 +34,12 @@ Functions and classes provided: While many objects natively support use in with statements, sometimes a resource needs to be managed that isn't a context manager in its own right, - and doesn't implement a ``close()`` method for use with ``contextlib.closing`` + and doesn't implement a ``close()`` method for use with ``contextlib2.closing`` An abstract example would be the following to ensure correct resource management:: - from contextlib import contextmanager + from contextlib2 import contextmanager @contextmanager def managed_resource(*args, **kwds): @@ -83,7 +83,7 @@ Functions and classes provided: .. decorator:: asynccontextmanager - Similar to :func:`~contextlib.contextmanager`, but creates an + Similar to :func:`~contextlib2.contextmanager`, but creates an :ref:`asynchronous context manager `. This function is a :term:`decorator` that can be used to define a factory @@ -94,7 +94,7 @@ Functions and classes provided: A simple example:: - from contextlib import asynccontextmanager + from contextlib2 import asynccontextmanager @asynccontextmanager async def get_connection(): @@ -117,7 +117,7 @@ Functions and classes provided: either as decorators or with :keyword:`async with` statements:: import time - from contextlib import asynccontextmanager + from contextlib2 import asynccontextmanager @asynccontextmanager async def timeit(): @@ -142,7 +142,7 @@ Functions and classes provided: Return a context manager that closes *thing* upon completion of the block. This is basically equivalent to:: - from contextlib import contextmanager + from contextlib2 import contextmanager @contextmanager def closing(thing): @@ -153,7 +153,7 @@ Functions and classes provided: And lets you write code like this:: - from contextlib import closing + from contextlib2 import closing from urllib.request import urlopen with closing(urlopen('https://www.python.org')) as page: @@ -177,7 +177,7 @@ Functions and classes provided: Return an async context manager that calls the ``aclose()`` method of *thing* upon completion of the block. This is basically equivalent to:: - from contextlib import asynccontextmanager + from contextlib2 import asynccontextmanager @asynccontextmanager async def aclosing(thing): @@ -190,7 +190,7 @@ Functions and classes provided: generators when they happen to exit early by :keyword:`break` or an exception. For example:: - from contextlib import aclosing + from contextlib2 import aclosing async with aclosing(my_generator()) as values: async for value in values: @@ -217,10 +217,10 @@ Functions and classes provided: def myfunction(arg, ignore_exceptions=False): if ignore_exceptions: # Use suppress to ignore all exceptions. - cm = contextlib.suppress(Exception) + cm = contextlib2.suppress(Exception) else: # Do not ignore any exceptions, cm has no effect. - cm = contextlib.nullcontext() + cm = contextlib2.nullcontext() with cm: # Do something @@ -272,7 +272,7 @@ Functions and classes provided: For example:: - from contextlib import suppress + from contextlib2 import suppress with suppress(FileNotFoundError): os.remove('somefile.tmp') @@ -349,7 +349,7 @@ Functions and classes provided: .. function:: redirect_stderr(new_target) - Similar to :func:`~contextlib.redirect_stdout` but redirecting + Similar to :func:`~contextlib2.redirect_stdout` but redirecting :data:`sys.stderr` to another file or file-like object. This context manager is :ref:`reentrant `. @@ -389,7 +389,7 @@ Functions and classes provided: Example of ``ContextDecorator``:: - from contextlib import ContextDecorator + from contextlib2 import ContextDecorator class mycontext(ContextDecorator): def __enter__(self): @@ -436,7 +436,7 @@ Functions and classes provided: Existing context managers that already have a base class can be extended by using ``ContextDecorator`` as a mixin class:: - from contextlib import ContextDecorator + from contextlib2 import ContextDecorator class mycontext(ContextBaseClass, ContextDecorator): def __enter__(self): @@ -459,7 +459,7 @@ Functions and classes provided: Example of ``AsyncContextDecorator``:: from asyncio import run - from contextlib import AsyncContextDecorator + from contextlib2 import AsyncContextDecorator class mycontext(AsyncContextDecorator): async def __aenter__(self): @@ -657,7 +657,7 @@ Examples and Recipes -------------------- This section describes some examples and recipes for making effective use of -the tools provided by :mod:`contextlib`. +the tools provided by :mod:`contextlib2`. Supporting a variable number of context managers @@ -721,7 +721,7 @@ Here's an example of doing this for a context manager that accepts resource acquisition and release functions, along with an optional validation function, and maps them to the context management protocol:: - from contextlib import contextmanager, AbstractContextManager, ExitStack + from contextlib2 import contextmanager, AbstractContextManager, ExitStack class ResourceManager(AbstractContextManager): @@ -781,7 +781,7 @@ up being separated by arbitrarily long sections of code. execution at the end of a ``with`` statement, and then later decide to skip executing that callback:: - from contextlib import ExitStack + from contextlib2 import ExitStack with ExitStack() as stack: stack.callback(cleanup_resources) @@ -795,7 +795,7 @@ rather than requiring a separate flag variable. If a particular application uses this pattern a lot, it can be simplified even further by means of a small helper class:: - from contextlib import ExitStack + from contextlib2 import ExitStack class Callback(ExitStack): def __init__(self, callback, /, *args, **kwds): @@ -815,7 +815,7 @@ function, then it is still possible to use the decorator form of :meth:`ExitStack.callback` to declare the resource cleanup in advance:: - from contextlib import ExitStack + from contextlib2 import ExitStack with ExitStack() as stack: @stack.callback @@ -842,7 +842,7 @@ writing both a function decorator and a context manager for the task, inheriting from :class:`ContextDecorator` provides both capabilities in a single definition:: - from contextlib import ContextDecorator + from contextlib2 import ContextDecorator import logging logging.basicConfig(level=logging.INFO) @@ -904,7 +904,7 @@ Context managers created using :func:`contextmanager` are also single use context managers, and will complain about the underlying generator failing to yield if an attempt is made to use them a second time:: - >>> from contextlib import contextmanager + >>> from contextlib2 import contextmanager >>> @contextmanager ... def singleuse(): ... print("Before") @@ -939,7 +939,7 @@ using the same context manager. :func:`suppress`, :func:`redirect_stdout`, and :func:`chdir`. Here's a very simple example of reentrant use:: - >>> from contextlib import redirect_stdout + >>> from contextlib2 import redirect_stdout >>> from io import StringIO >>> stream = StringIO() >>> write_to_stream = redirect_stdout(stream) @@ -985,7 +985,7 @@ Another example of a reusable, but not reentrant, context manager is when leaving any with statement, regardless of where those callbacks were added:: - >>> from contextlib import ExitStack + >>> from contextlib2 import ExitStack >>> stack = ExitStack() >>> with stack: ... stack.callback(print, "Callback: from first context") @@ -1019,7 +1019,7 @@ statement, which is unlikely to be desirable behaviour. Using separate :class:`ExitStack` instances instead of reusing a single instance avoids that problem:: - >>> from contextlib import ExitStack + >>> from contextlib2 import ExitStack >>> with ExitStack() as outer_stack: ... outer_stack.callback(print, "Callback: from outer context") ... with ExitStack() as inner_stack: diff --git a/test/test_contextlib.py b/test/test_contextlib.py index 2034eac..32e1550 100644 --- a/test/test_contextlib.py +++ b/test/test_contextlib.py @@ -1,4 +1,4 @@ -"""Unit tests for contextlib.py, and other context managers.""" +"""Unit tests for synchronous features of contextlib2.py""" import io import os diff --git a/test/test_contextlib_async.py b/test/test_contextlib_async.py index b24cc8b..42b5502 100644 --- a/test/test_contextlib_async.py +++ b/test/test_contextlib_async.py @@ -1,3 +1,5 @@ +"""Unit tests for asynchronous features of contextlib2.py""" + import asyncio from contextlib2 import ( asynccontextmanager, AbstractAsyncContextManager,