Update to Python 3.4 alpha version

This commit is contained in:
Nick Coghlan 2013-10-26 21:42:03 +10:00
parent 21a5d8b0a4
commit 30515dbe4f
5 changed files with 782 additions and 168 deletions

View file

@ -1,8 +1,19 @@
Release History
---------------
0.4.0 (2012-05-??)
~~~~~~~~~~~~~~~~~~
0.5.0 (2013-??-??)
^^^^^^^^^^^^^^^^^^
* Updated to include all features from the upcoming Python 3.4 version of
contextlib (also includes some ``ExitStack`` enhancements made following
the integration into the standard library for Python 3.3)
* The legacy ``ContextStack`` and ``ContextDecorator.refresh_cm`` APIs are
no longer documented
0.4.0 (2012-05-05)
^^^^^^^^^^^^^^^^^^
* Issue #8: Replace ContextStack with ExitStack (old ContextStack API
retained for backwards compatibility)
@ -10,13 +21,13 @@ Release History
0.3.1 (2012-01-17)
~~~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^^^
* Issue #7: Add MANIFEST.in so PyPI package contains all relevant files
0.3 (2012-01-04)
~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^
* Issue #5: ContextStack.register no longer pointlessly returns the wrapped
function
@ -32,14 +43,14 @@ Release History
0.2 (2011-12-15)
~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^
* Renamed CleanupManager to ContextStack (hopefully before anyone started
using the module for anything, since I didn't alias the old name at all)
0.1 (2011-12-13)
~~~~~~~~~~~~~~~~
^^^^^^^^^^^^^^^^
* Initial release as a backport module
* Added CleanupManager (based on a `Python feature request`_)

View file

@ -1 +1 @@
0.4.0
0.5.0

View file

@ -4,17 +4,31 @@ import sys
from collections import deque
from functools import wraps
__all__ = ["contextmanager", "closing", "ContextDecorator",
"ContextStack", "ExitStack"]
# Standard library exports
__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",
"redirect_stdout", "suppress"]
# contextlib2 only exports (current and legacy experimental interfaces)
__all__ += ["ContextStack"]
class ContextDecorator(object):
"A base class or mixin that enables context managers to work as decorators."
def _recreate_cm(self):
"""Return a recreated instance of self.
Allows an otherwise one-shot context manager like
_GeneratorContextManager to support use as
a decorator via implicit recreation.
This is a private interface just for _GeneratorContextManager.
See issue #11647 for details.
"""
return self
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
@ -37,6 +51,16 @@ class _GeneratorContextManager(ContextDecorator):
def __init__(self, func, *args, **kwds):
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = func, args, kwds
# Issue 19330: ensure context manager instances have good docstrings
doc = getattr(func, "__doc__", None)
if doc is None:
doc = type(self).__doc__
self.__doc__ = doc
# Unfortunately, this still doesn't provide good help output when
# inspecting the created context manager instances, since pydoc
# currently bypasses the instance docstring and shows the docstring
# for the class instead.
# See http://bugs.python.org/issue19404 for more details.
def refresh_cm(self):
# _GCM instances are one-shot context managers, so the
@ -141,23 +165,116 @@ class closing(object):
def __exit__(self, *exc_info):
self.thing.close()
class redirect_stdout:
"""Context manager for temporarily redirecting stdout to another file
# How to send help() to stderr
with redirect_stdout(sys.stderr):
help(dir)
# How to write help() to a file
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
"""
def __init__(self, new_target):
self._new_target = new_target
self._old_target = self._sentinel = object()
def __enter__(self):
if self._old_target is not self._sentinel:
raise RuntimeError("Cannot reenter {!r}".format(self))
self._old_target = sys.stdout
sys.stdout = self._new_target
return self._new_target
def __exit__(self, exctype, excinst, exctb):
restore_stdout = self._old_target
self._old_target = self._sentinel
sys.stdout = restore_stdout
class suppress:
"""Context manager to suppress specified exceptions
After the exception is suppressed, execution proceeds with the next
statement following the with statement.
with suppress(FileNotFoundError):
os.remove(somefile)
# Execution still resumes here if the file was already removed
"""
def __init__(self, *exceptions):
self._exceptions = exceptions
def __enter__(self):
pass
def __exit__(self, exctype, excinst, exctb):
# Unlike isinstance and issubclass, CPython exception handling
# currently only looks at the concrete type hierarchy (ignoring
# the instance and subclass checking hooks). While Guido considers
# that a bug rather than a feature, it's a fairly hard one to fix
# due to various internal implementation details. suppress provides
# the simpler issubclass based semantics, rather than trying to
# exactly reproduce the limitations of the CPython interpreter.
#
# See http://bugs.python.org/issue12029 for more details
return exctype is not None and issubclass(exctype, self._exceptions)
# Context manipulation is Python 3 only
if str is not bytes:
def _make_context_fixer(frame_exc):
def _fix_exception_context(new_exc, old_exc):
while 1:
exc_context = new_exc.__context__
if exc_context in (None, frame_exc):
break
new_exc = exc_context
new_exc.__context__ = old_exc
return _fix_exception_context
def _reraise_with_existing_context(exc_details):
try:
# bare "raise exc_details[1]" replaces our carefully
# set-up context
fixed_ctx = exc_details[1].__context__
raise exc_details[1]
except BaseException:
exc_details[1].__context__ = fixed_ctx
raise
else:
# No exception context in Python 2
def _make_context_fixer(frame_exc):
return lambda new_exc, old_exc: None
# Use 3 argument raise in Python 2,
# but use exec to avoid SyntaxError in Python 3
def _reraise_with_existing_context(exc_details):
exc_type, exc_value, exc_tb = exc_details
exec ("raise exc_type, exc_value, exc_tb")
# Inspired by discussions on http://bugs.python.org/issue13585
class ExitStack(object):
"""Context manager for dynamic management of a stack of exit callbacks
For example:
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
# All opened files will automatically be closed at the end of
# the with statement, even if attempts to open files later
# in the list throw an exception
# in the list raise an exception
"""
def __init__(self):
self._exit_callbacks = deque()
def pop_all(self):
"""Preserve the context stack by transferring it to a new instance"""
new_stack = type(self)()
@ -171,14 +288,14 @@ class ExitStack(object):
return cm_exit(cm, *exc_details)
_exit_wrapper.__self__ = cm
self.push(_exit_wrapper)
def push(self, exit):
"""Registers a callback with the standard __exit__ method signature
Can suppress exceptions the same way __exit__ methods can.
Also accepts any object with an __exit__ method (registering the
method instead of the object itself)
Also accepts any object with an __exit__ method (registering a call
to the method instead of the object itself)
"""
# We use an unbound method rather than a bound method to follow
# the standard lookup behaviour for special methods
@ -194,7 +311,7 @@ class ExitStack(object):
def callback(self, callback, *args, **kwds):
"""Registers an arbitrary callback and arguments.
Cannot suppress exceptions.
"""
def _exit_wrapper(exc_type, exc, tb):
@ -207,7 +324,7 @@ class ExitStack(object):
def enter_context(self, cm):
"""Enters the supplied context manager
If successful, also pushes its __exit__ method as a callback and
returns the result of the __enter__ method.
"""
@ -226,35 +343,33 @@ class ExitStack(object):
return self
def __exit__(self, *exc_details):
if not self._exit_callbacks:
return
# This looks complicated, but it is really just
# setting up a chain of try-expect statements to ensure
# that outer callbacks still get invoked even if an
# inner one throws an exception
def _invoke_next_callback(exc_details):
# Callbacks are removed from the list in FIFO order
# but the recursion means they're invoked in LIFO order
cb = self._exit_callbacks.popleft()
if not self._exit_callbacks:
# Innermost callback is invoked directly
return cb(*exc_details)
# More callbacks left, so descend another level in the stack
received_exc = exc_details[0] is not None
# We manipulate the exception state so it behaves as though
# we were actually nesting multiple with statements
frame_exc = sys.exc_info()[1]
_fix_exception_context = _make_context_fixer(frame_exc)
# Callbacks are invoked in LIFO order to match the behaviour of
# nested context managers
suppressed_exc = False
pending_raise = False
while self._exit_callbacks:
cb = self._exit_callbacks.pop()
try:
suppress_exc = _invoke_next_callback(exc_details)
except:
suppress_exc = cb(*sys.exc_info())
# Check if this cb suppressed the inner exception
if not suppress_exc:
raise
else:
# Check if inner cb suppressed the original exception
if suppress_exc:
if cb(*exc_details):
suppressed_exc = True
pending_raise = False
exc_details = (None, None, None)
suppress_exc = cb(*exc_details) or suppress_exc
return suppress_exc
# Kick off the recursive chain
return _invoke_next_callback(exc_details)
except:
new_exc_details = sys.exc_info()
# simulate the stack of exceptions by setting the context
_fix_exception_context(new_exc_details[1], exc_details[1])
pending_raise = True
exc_details = new_exc_details
if pending_raise:
_reraise_with_existing_context(exc_details)
return received_exc and suppressed_exc
# Preserve backwards compatibility
class ContextStack(ExitStack):

View file

@ -21,22 +21,25 @@ involving the ``with`` statement.
Additions Relative to the Standard Library
------------------------------------------
This module is primarily a backport of the Python 3.2 version of
This module is primarily a backport of the Python 3.4 version of
:mod:`contextlib` to earlier releases. However, it is also a proving ground
for new features not yet part of the standard library. Those new features
are currently:
for new features not yet part of the standard library.
* :class:`ExitStack`
* :meth:`ContextDecorator.refresh_cm`
There are currently no such features in the module.
Refer to the :mod:`contextlib` documentation for details of which
versions of Python 3 include the various APIs provided in this module.
API Reference
=============
.. function:: contextmanager
Functions and classes provided:
This function is a decorator that can be used to define a factory
function for ``with`` statement context managers, without needing to
.. decorator:: contextmanager
This function is a :term:`decorator` that can be used to define a factory
function for :keyword:`with` statement context managers, without needing to
create a class or separate :meth:`__enter__` and :meth:`__exit__` methods.
A simple example (this is not recommended as a real way of generating HTML!)::
@ -56,24 +59,24 @@ API Reference
foo
</h1>
The function being decorated must return a generator-iterator when
The function being decorated must return a :term:`generator`-iterator when
called. This iterator must yield exactly one value, which will be bound to
the targets in the ``with`` statement's ``as`` clause, if any.
the targets in the :keyword:`with` statement's :keyword:`as` clause, if any.
At the point where the generator yields, the block nested in the ``with``
At the point where the generator yields, the block nested in the :keyword:`with`
statement is executed. The generator is then resumed after the block is exited.
If an unhandled exception occurs in the block, it is reraised inside the
generator at the point where the yield occurred. Thus, you can use a
``try``...\ ``except``...\ ``finally`` statement to trap
:keyword:`try`...\ :keyword:`except`...\ :keyword:`finally` statement to trap
the error (if any), or ensure that some cleanup takes place. If an exception is
trapped merely in order to log it or to perform some action (rather than to
suppress it entirely), the generator must reraise that exception. Otherwise the
generator context manager will indicate to the ``with`` statement that
generator context manager will indicate to the :keyword:`with` statement that
the exception has been handled, and execution will resume with the statement
immediately following the ``with`` statement.
immediately following the :keyword:`with` statement.
:func:`contextmanager` uses :class:`ContextDecorator` so the context managers
it creates can be used as decorators as well as in ``with`` statements.
it creates can be used as decorators as well as in :keyword:`with` statements.
When used as a decorator, a new generator instance is implicitly created on
each function call (this allows the otherwise "one-shot" context managers
created by :func:`contextmanager` to meet the requirement that context
@ -104,7 +107,86 @@ API Reference
print(line)
without needing to explicitly close ``page``. Even if an error occurs,
``page.close()`` will be called when the ``with`` block is exited.
``page.close()`` will be called when the :keyword:`with` block is exited.
.. function:: suppress(*exceptions)
Return a context manager that suppresses any of the specified exceptions
if they occur in the body of a with statement and then resumes execution
with the first statement following the end of the with statement.
As with any other mechanism that completely suppresses exceptions, this
context manager should be used only to cover very specific errors where
silently continuing with program execution is known to be the right
thing to do.
For example::
from contextlib import suppress
with suppress(FileNotFoundError):
os.remove('somefile.tmp')
with suppress(FileNotFoundError):
os.remove('someotherfile.tmp')
This code is equivalent to::
try:
os.remove('somefile.tmp')
except FileNotFoundError:
pass
try:
os.remove('someotherfile.tmp')
except FileNotFoundError:
pass
This context manager is :ref:`reentrant <reentrant-cms>`.
.. versionadded:: 0.5
Part of the standard library in Python 3.4 and later
.. function:: redirect_stdout(new_target)
Context manager for temporarily redirecting :data:`sys.stdout` to
another file or file-like object.
This tool adds flexibility to existing functions or classes whose output
is hardwired to stdout.
For example, the output of :func:`help` normally is sent to *sys.stdout*.
You can capture that output in a string by redirecting the output to a
:class:`io.StringIO` object::
f = io.StringIO()
with redirect_stdout(f):
help(pow)
s = f.getvalue()
To send the output of :func:`help` to a file on disk, redirect the output
to a regular file::
with open('help.txt', 'w') as f:
with redirect_stdout(f):
help(pow)
To send the output of :func:`help` to *sys.stderr*::
with redirect_stdout(sys.stderr):
help(pow)
Note that the global side effect on :data:`sys.stdout` means that this
context manager is not suitable for use in library code and most threaded
applications. It also has no effect on the output of subprocesses.
However, it is still a useful approach for many utility scripts.
This context manager is :ref:`reusable but not reentrant <reusable-cms>`.
.. versionadded:: 0.5
Part of the standard library in Python 3.4 and later
.. class:: ContextDecorator()
@ -112,7 +194,7 @@ API Reference
A base class that enables a context manager to also be used as a decorator.
Context managers inheriting from ``ContextDecorator`` have to implement
:meth:`__enter__` and :meth:`__exit__` as normal. :meth:`__exit__` retains its optional
``__enter__`` and ``__exit__`` as normal. ``__exit__`` retains its optional
exception handling even when used as a decorator.
``ContextDecorator`` is used by :func:`contextmanager`, so you get this
@ -174,22 +256,11 @@ API Reference
def __exit__(self, *exc):
return False
.. method:: refresh_cm()
This method is invoked each time a call is made to a decorated function.
The default implementation just returns *self*.
.. note::
As the decorated function must be able to be called multiple times, the
underlying context manager must normally support use in multiple
``with`` statements (preferably in a thread-safe manner). If
this is not the case, then the context manager must define this method
and return a *new* copy of the context manager on each invocation.
This may involve keeping a copy of the original arguments used to
first initialise the context manager.
.. versionchanged:: 0.1
Made the standard library's private :meth:`refresh_cm` API public
underlying context manager must support use in multiple :keyword:`with`
statements. If this is not the case, then the original construct with the
explicit :keyword:`with` statement inside the function should be used.
.. class:: ExitStack()
@ -205,20 +276,32 @@ API Reference
files = [stack.enter_context(open(fname)) for fname in filenames]
# All opened files will automatically be closed at the end of
# the with statement, even if attempts to open files later
# in the list throw an exception
# in the list raise an exception
Each instance maintains a stack of registered callbacks that are called in
reverse order when the instance is closed (either explicitly or implicitly
at the end of a ``with`` statement). Note that callbacks are *not* invoked
implicitly when the context stack instance is garbage collected.
at the end of a :keyword:`with` statement). Note that callbacks are *not*
invoked implicitly when the context stack instance is garbage collected.
This stack model is used so that context managers that acquire their
resources in their ``__init__`` method (such as file objects) can be
handled correctly.
Since registered callbacks are invoked in the reverse order of
registration, this ends up behaving as if multiple nested ``with``
registration, this ends up behaving as if multiple nested :keyword:`with`
statements had been used with the registered set of callbacks. This even
extends to exception handling - if an inner callback suppresses or replaces
an exception, then outer callbacks will be passed arguments based on that
updated state.
This is a relatively low level API that takes care of the details of
correctly unwinding the stack of exit callbacks. It provides a suitable
foundation for higher level context managers that manipulate the exit
stack in application specific ways.
.. versionadded:: 0.4
Part of the standard library in Python 3.3 and later
.. method:: enter_context(cm)
Enters a new context manager and adds its :meth:`__exit__` method to
@ -226,21 +309,25 @@ API Reference
manager's own :meth:`__enter__` method.
These context managers may suppress exceptions just as they normally
would if used directly as part of a ``with`` statement.
would if used directly as part of a :keyword:`with` statement.
.. method:: push(exit)
Directly accepts a callback with the same signature as a
context manager's :meth:`__exit__` method and adds it to the callback
stack.
Adds a context manager's :meth:`__exit__` method to the callback stack.
As ``__enter__`` is *not* invoked, this method can be used to cover
part of an :meth:`__enter__` implementation with a context manager's own
:meth:`__exit__` method.
If passed an object that is not a context manager, this method assumes
it is a callback with the same signature as a context manager's
:meth:`__exit__` method and adds it directly to the callback stack.
By returning true values, these callbacks can suppress exceptions the
same way context manager :meth:`__exit__` methods can.
This method also accepts any object with an ``__exit__`` method, and
will register that method as the callback. This is mainly useful to
cover part of an :meth:`__enter__` implementation with a context
manager's own :meth:`__exit__` method.
The passed in object is returned from the function, allowing this
method to be used as a function decorator.
.. method:: callback(callback, *args, **kwds)
@ -250,22 +337,27 @@ API Reference
Unlike the other methods, callbacks added this way cannot suppress
exceptions (as they are never passed the exception details).
The passed in callback is returned from the function, allowing this
method to be used as a function decorator.
.. method:: pop_all()
Transfers the callback stack to a fresh instance and returns it. No
callbacks are invoked by this operation - instead, they will now be
invoked when the new stack is closed (either explicitly or implicitly).
Transfers the callback stack to a fresh :class:`ExitStack` instance
and returns it. No callbacks are invoked by this operation - instead,
they will now be invoked when the new stack is closed (either
explicitly or implicitly at the end of a :keyword:`with` statement).
For example, a group of files can be opened as an "all or nothing"
operation as follows::
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
# Hold onto the close method, but don't call it yet.
close_files = stack.pop_all().close
# If opening any file fails, all previously opened files will be
# closed automatically. If all files are opened successfully,
# they will remain open even after the with statement ends.
# close_files() can then be invoked explicitly to close them all
# close_files() can then be invoked explicitly to close them all.
.. method:: close()
@ -274,21 +366,6 @@ API Reference
callbacks registered, the arguments passed in will indicate that no
exception occurred.
.. versionadded:: 0.4
New API for :mod:`contextlib2`, not available in standard library
.. class:: ContextStack()
An earlier incarnation of the :class:`ExitStack` interface. This class
is deprecated and should no longer be used.
.. versionchanged:: 0.4
Deprecated in favour of :class:`ExitStack`
.. versionadded:: 0.2
New API for :mod:`contextlib2`, not available in standard library
Examples and Recipes
====================
@ -299,53 +376,6 @@ the tools provided by :mod:`contextlib2`. Some of them may also work with
case, it is noted at the end of the example.
Using a context manager as a function decorator
-----------------------------------------------
:class:`ContextDecorator` makes it possible to use a context manager in
both an ordinary ``with`` statement and also as a function decorator. The
:meth:`ContextDecorator.refresh_cm` method even makes it possible to use
otherwise single use context managers (such as those created by
:func:`contextmanager`) that way.
For example, it is sometimes useful to wrap functions or groups of statements
with a logger that can track the time of entry and time of exit. Rather than
writing both a function decorator and a context manager for the task,
:func:`contextmanager` provides both capabilities in a single
definition::
from contextlib2 import contextmanager
import logging
logging.basicConfig(level=logging.INFO)
@contextmanager
def track_entry_and_exit(name):
logging.info('Entering: {}'.format(name))
yield
logging.info('Exiting: {}'.format(name))
This can be used as both a context manager::
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
And also as a function decorator::
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
Note that there is one additional limitation when using context managers
as function decorators: there's no way to access the return value of
:meth:`__enter__`. If that value is needed, then it is still necessary to use
an explicit ``with`` statement.
This example should also work with :mod:`contextlib` in Python 3.2.1 or later.
Cleaning up in an ``__enter__`` implementation
----------------------------------------------
@ -384,6 +414,8 @@ and maps them to the context management protocol::
# We don't need to duplicate any of our resource release logic
self.release_resource()
This example will also work with :mod:`contextlib` in Python 3.3 or later.
Replacing any use of ``try-finally`` and flag variables
-------------------------------------------------------
@ -456,7 +488,174 @@ advance::
Due to the way the decorator protocol works, a callback function
declared this way cannot take any parameters. Instead, any resources to
be released must be accessed as closure variables
be released must be accessed as closure variables.
This example will also work with :mod:`contextlib` in Python 3.3 or later.
Using a context manager as a function decorator
-----------------------------------------------
:class:`ContextDecorator` makes it possible to use a context manager in
both an ordinary ``with`` statement and also as a function decorator. The
:meth:`ContextDecorator.refresh_cm` method even makes it possible to use
otherwise single use context managers (such as those created by
:func:`contextmanager`) that way.
For example, it is sometimes useful to wrap functions or groups of statements
with a logger that can track the time of entry and time of exit. Rather than
writing both a function decorator and a context manager for the task,
:func:`contextmanager` provides both capabilities in a single
definition::
from contextlib2 import contextmanager
import logging
logging.basicConfig(level=logging.INFO)
@contextmanager
def track_entry_and_exit(name):
logging.info('Entering: {}'.format(name))
yield
logging.info('Exiting: {}'.format(name))
This can be used as both a context manager::
with track_entry_and_exit('widget loader'):
print('Some time consuming activity goes here')
load_widget()
And also as a function decorator::
@track_entry_and_exit('widget loader')
def activity():
print('Some time consuming activity goes here')
load_widget()
Note that there is one additional limitation when using context managers
as function decorators: there's no way to access the return value of
:meth:`__enter__`. If that value is needed, then it is still necessary to use
an explicit ``with`` statement.
This example will also work with :mod:`contextlib` in Python 3.2.1 or later.
Context Management Concepts
===========================
.. _single-use-reusable-and-reentrant-cms:
Single use, reusable and reentrant context managers
---------------------------------------------------
Most context managers are written in a way that means they can only be
used effectively in a :keyword:`with` statement once. These single use
context managers must be created afresh each time they're used -
attempting to use them a second time will trigger an exception or
otherwise not work correctly.
This common limitation means that it is generally advisable to create
context managers directly in the header of the :keyword:`with` statement
where they are used (as shown in all of the usage examples above).
Files are an example of effectively single use context managers, since
the first :keyword:`with` statement will close the file, preventing any
further IO operations using that file object.
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
>>> @contextmanager
... def singleuse():
... print("Before")
... yield
... print("After")
...
>>> cm = singleuse()
>>> with cm:
... pass
...
Before
After
>>> with cm:
... pass
...
Traceback (most recent call last):
...
RuntimeError: generator didn't yield
.. _reentrant-cms:
Reentrant context managers
^^^^^^^^^^^^^^^^^^^^^^^^^^
More sophisticated context managers may be "reentrant". These context
managers can not only be used in multiple :keyword:`with` statements,
but may also be used *inside* a :keyword:`with` statement that is already
using the same context manager.
:class:`threading.RLock` is an example of a reentrant context manager, as is
:func:`suppress`. Here's a toy example of reentrant use (real world
examples of reentrancy are more likely to occur with objects like recursive
locks and are likely to be far more complicated than this example)::
>>> from contextlib import suppress
>>> ignore_raised_exception = suppress(ZeroDivisionError)
>>> with ignore_raised_exception:
... with ignore_raised_exception:
... 1/0
... print("This line runs")
... 1/0
... print("This is skipped")
...
This line runs
>>> # The second exception is also suppressed
.. _reusable-cms:
Reusable context managers
^^^^^^^^^^^^^^^^^^^^^^^^^
Distinct from both single use and reentrant context managers are "reusable"
context managers (or, to be completely explicit, "reusable, but not
reentrant" context managers, since reentrant context managers are also
reusable). These context managers support being used multiple times, but
will fail (or otherwise not work correctly) if the specific context manager
instance has already been used in a containing with statement.
An example of a reusable context manager is :func:`redirect_stdout`::
>>> from contextlib import redirect_stdout
>>> from io import StringIO
>>> f = StringIO()
>>> collect_output = redirect_stdout(f)
>>> with collect_output:
... print("Collected")
...
>>> print("Not collected")
Not collected
>>> with collect_output:
... print("Also collected")
...
>>> print(f.getvalue())
Collected
Also collected
However, this context manager is not reentrant, so attempting to reuse it
within a containing with statement fails:
>>> with collect_output:
... # Nested reuse is not permitted
... with collect_output:
... pass
...
Traceback (most recent call last):
...
RuntimeError: Cannot reenter <...>
Obtaining the Module

View file

@ -1,17 +1,51 @@
#!/usr/bin/env python
"""Unit tests for contextlib2"""
import io
import sys
# Check for a sufficiently recent unittest module
import unittest
if not hasattr(unittest, "skipIf"):
import unittest2 as unittest
# Handle 2/3 compatibility for redirect_stdout testing,
# checking 3.x only implicit exception chaining behaviour
# and checking for exception details in test cases
if str is bytes:
# Python 2
from io import BytesIO as StrIO
check_exception_chaining = False
def check_exception_details(case, exc_type, regex):
return case.assertRaisesRegexp(exc_type, regex)
else:
# Python 3
from io import StringIO as StrIO
check_exception_chaining = True
def check_exception_details(case, exc_type, regex):
return case.assertRaisesRegex(exc_type, regex)
from contextlib2 import * # Tests __all__
class ContextManagerTestCase(unittest.TestCase):
def test_instance_docstring_given_function_docstring(self):
# Issue 19330: ensure context manager instances have good docstrings
# See http://bugs.python.org/issue19404 for why this doesn't currently
# affect help() output :(
def gen_with_docstring():
"""This has a docstring"""
yield
gen_docstring = gen_with_docstring.__doc__
cm_with_docstring = contextmanager(gen_with_docstring)
self.assertEqual(cm_with_docstring.__doc__, gen_docstring)
obj = cm_with_docstring()
self.assertEqual(obj.__doc__, gen_docstring)
self.assertNotEqual(obj.__doc__, type(obj).__doc__)
def test_contextmanager_plain(self):
state = []
@contextmanager
@ -107,7 +141,11 @@ class ContextManagerTestCase(unittest.TestCase):
class ClosingTestCase(unittest.TestCase):
# XXX This needs more work
def test_instance_docs(self):
# Issue 19330: ensure context manager instances have good docstrings
cm_docstring = closing.__doc__
obj = closing(None)
self.assertEqual(obj.__doc__, cm_docstring)
def test_closing(self):
state = []
@ -135,6 +173,7 @@ class ClosingTestCase(unittest.TestCase):
class mycontext(ContextDecorator):
"""Example decoration-compatible context manager for testing"""
started = False
exc = None
catch = False
@ -150,6 +189,12 @@ class mycontext(ContextDecorator):
class TestContextDecorator(unittest.TestCase):
def test_instance_docs(self):
# Issue 19330: ensure context manager instances have good docstrings
cm_docstring = mycontext.__doc__
obj = mycontext()
self.assertEqual(obj.__doc__, cm_docstring)
def test_contextdecorator(self):
context = mycontext()
with context as result:
@ -162,12 +207,11 @@ class TestContextDecorator(unittest.TestCase):
def test_contextdecorator_with_exception(self):
context = mycontext()
with self.assertRaises(NameError):
with check_exception_details(self, NameError, 'foo'):
with context:
raise NameError('foo')
self.assertIsNotNone(context.exc)
self.assertIs(context.exc[0], NameError)
self.assertIn('foo', str(context.exc[1]))
context = mycontext()
context.catch = True
@ -197,11 +241,10 @@ class TestContextDecorator(unittest.TestCase):
self.assertTrue(context.started)
raise NameError('foo')
with self.assertRaises(NameError):
with check_exception_details(self, NameError, 'foo'):
test()
self.assertIsNotNone(context.exc)
self.assertIs(context.exc[0], NameError)
self.assertIn('foo', str(context.exc[1]))
def test_decorating_method(self):
@ -305,6 +348,12 @@ class TestContextDecorator(unittest.TestCase):
class TestExitStack(unittest.TestCase):
def test_instance_docs(self):
# Issue 19330: ensure context manager instances have good docstrings
cm_docstring = ExitStack.__doc__
obj = ExitStack()
self.assertEqual(obj.__doc__, cm_docstring)
def test_no_resources(self):
with ExitStack():
pass
@ -416,6 +465,154 @@ class TestExitStack(unittest.TestCase):
new_stack.close()
self.assertEqual(result, [1, 2, 3])
def test_exit_raise(self):
with self.assertRaises(ZeroDivisionError):
with ExitStack() as stack:
stack.push(lambda *exc: False)
1/0
def test_exit_suppress(self):
with ExitStack() as stack:
stack.push(lambda *exc: True)
1/0
def test_exit_exception_chaining_reference(self):
# Sanity check to make sure that ExitStack chaining matches
# actual nested with statements
# We still run this under Py2, but the only thing it actually
# checks in that case is that the outermost exception is IndexError
# and that the inner ValueError was suppressed
class RaiseExc(object):
def __init__(self, exc):
self.exc = exc
def __enter__(self):
return self
def __exit__(self, *exc_details):
raise self.exc
class RaiseExcWithContext(object):
def __init__(self, outer, inner):
self.outer = outer
self.inner = inner
def __enter__(self):
return self
def __exit__(self, *exc_details):
try:
raise self.inner
except:
raise self.outer
class SuppressExc(object):
def __enter__(self):
return self
def __exit__(self, *exc_details):
type(self).saved_details = exc_details
return True
try:
with RaiseExc(IndexError):
with RaiseExcWithContext(KeyError, AttributeError):
with SuppressExc():
with RaiseExc(ValueError):
1 / 0
except IndexError as exc:
if check_exception_chaining:
self.assertIsInstance(exc.__context__, KeyError)
self.assertIsInstance(exc.__context__.__context__, AttributeError)
# Inner exceptions were suppressed
self.assertIsNone(exc.__context__.__context__.__context__)
else:
self.fail("Expected IndexError, but no exception was raised")
# Check the inner exceptions
inner_exc = SuppressExc.saved_details[1]
self.assertIsInstance(inner_exc, ValueError)
if check_exception_chaining:
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
def test_exit_exception_chaining(self):
# Ensure exception chaining matches the reference behaviour
# We still run this under Py2, but the only thing it actually
# checks in that case is that the outermost exception is IndexError
# and that the inner ValueError was suppressed
def raise_exc(exc):
raise exc
saved_details = []
def suppress_exc(*exc_details):
saved_details[:] = [exc_details]
return True
try:
with ExitStack() as stack:
stack.callback(raise_exc, IndexError)
stack.callback(raise_exc, KeyError)
stack.callback(raise_exc, AttributeError)
stack.push(suppress_exc)
stack.callback(raise_exc, ValueError)
1 / 0
except IndexError as exc:
if check_exception_chaining:
self.assertIsInstance(exc.__context__, KeyError)
self.assertIsInstance(exc.__context__.__context__, AttributeError)
# Inner exceptions were suppressed
self.assertIsNone(exc.__context__.__context__.__context__)
else:
self.fail("Expected IndexError, but no exception was raised")
# Check the inner exceptions
inner_exc = saved_details[0][1]
self.assertIsInstance(inner_exc, ValueError)
if check_exception_chaining:
self.assertIsInstance(inner_exc.__context__, ZeroDivisionError)
def test_exit_exception_non_suppressing(self):
# http://bugs.python.org/issue19092
def raise_exc(exc):
raise exc
def suppress_exc(*exc_details):
return True
try:
with ExitStack() as stack:
stack.callback(lambda: None)
stack.callback(raise_exc, IndexError)
except Exception as exc:
self.assertIsInstance(exc, IndexError)
else:
self.fail("Expected IndexError, but no exception was raised")
try:
with ExitStack() as stack:
stack.callback(raise_exc, KeyError)
stack.push(suppress_exc)
stack.callback(raise_exc, IndexError)
except Exception as exc:
self.assertIsInstance(exc, KeyError)
else:
self.fail("Expected KeyError, but no exception was raised")
def test_body_exception_suppress(self):
def suppress_exc(*exc_details):
return True
try:
with ExitStack() as stack:
stack.push(suppress_exc)
1/0
except IndexError as exc:
self.fail("Expected no exception, got IndexError")
def test_exit_exception_chaining_suppress(self):
with ExitStack() as stack:
stack.push(lambda *exc: True)
stack.push(lambda *exc: 1/0)
stack.push(lambda *exc: {}[1])
def test_excessive_nesting(self):
# The original implementation would die with RecursionError here
with ExitStack() as stack:
for i in range(10000):
stack.callback(int)
def test_instance_bypass(self):
class Example(object): pass
cm = Example()
@ -425,9 +622,102 @@ class TestExitStack(unittest.TestCase):
stack.push(cm)
self.assertIs(stack._exit_callbacks[-1], cm)
class TestRedirectStdout(unittest.TestCase):
def test_instance_docs(self):
# Issue 19330: ensure context manager instances have good docstrings
cm_docstring = redirect_stdout.__doc__
obj = redirect_stdout(None)
self.assertEqual(obj.__doc__, cm_docstring)
def test_redirect_to_string_io(self):
f = StrIO()
msg = "Consider an API like help(), which prints directly to stdout"
with redirect_stdout(f):
print(msg)
s = f.getvalue().strip()
self.assertEqual(s, msg)
def test_enter_result_is_target(self):
f = StrIO()
with redirect_stdout(f) as enter_result:
self.assertIs(enter_result, f)
def test_cm_is_reusable(self):
f = StrIO()
write_to_f = redirect_stdout(f)
with write_to_f:
print("Hello")
with write_to_f:
print("World!")
s = f.getvalue()
self.assertEqual(s, "Hello\nWorld!\n")
# If this is ever made reentrant, update the reusable-but-not-reentrant
# example at the end of the contextlib docs accordingly.
def test_nested_reentry_fails(self):
f = StrIO()
write_to_f = redirect_stdout(f)
with check_exception_details(self, RuntimeError, "Cannot reenter"):
with write_to_f:
print("Hello")
with write_to_f:
print("World!")
class TestSuppress(unittest.TestCase):
def test_instance_docs(self):
# Issue 19330: ensure context manager instances have good docstrings
cm_docstring = suppress.__doc__
obj = suppress()
self.assertEqual(obj.__doc__, cm_docstring)
def test_no_result_from_enter(self):
with suppress(ValueError) as enter_result:
self.assertIsNone(enter_result)
def test_no_exception(self):
with suppress(ValueError):
self.assertEqual(pow(2, 5), 32)
def test_exact_exception(self):
with suppress(TypeError):
len(5)
def test_exception_hierarchy(self):
with suppress(LookupError):
'Hello'[50]
def test_other_exception(self):
with self.assertRaises(ZeroDivisionError):
with suppress(TypeError):
1/0
def test_no_args(self):
with self.assertRaises(ZeroDivisionError):
with suppress():
1/0
def test_multiple_exception_args(self):
with suppress(ZeroDivisionError, TypeError):
1/0
with suppress(ZeroDivisionError, TypeError):
len(5)
def test_cm_is_reentrant(self):
ignore_exceptions = suppress(Exception)
with ignore_exceptions:
pass
with ignore_exceptions:
len(5)
with ignore_exceptions:
1/0
with ignore_exceptions: # Check nested usage
len(5)
class TestContextStack(unittest.TestCase):
def test_no_resources(self):
with ContextStack():
pass
@ -547,7 +837,6 @@ class TestContextStack(unittest.TestCase):
self.assertRaises(AttributeError, stack.enter_context, cm)
stack.register_exit(cm)
self.assertIs(stack._exit_callbacks[-1], cm)
if __name__ == "__main__":
import unittest
unittest.main(__name__)
unittest.main()