mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-16 21:50:24 +00:00
Update to Python 3.4 alpha version
This commit is contained in:
parent
21a5d8b0a4
commit
30515dbe4f
5 changed files with 782 additions and 168 deletions
23
NEWS.rst
23
NEWS.rst
|
|
@ -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`_)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
0.4.0
|
||||
0.5.0
|
||||
|
|
|
|||
195
contextlib2.py
195
contextlib2.py
|
|
@ -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):
|
||||
|
|
|
|||
421
docs/index.rst
421
docs/index.rst
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue