mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-05-24 06:33:45 +00:00
Merge from default
This commit is contained in:
commit
a254dc69e2
8 changed files with 966 additions and 295 deletions
|
|
@ -15,3 +15,4 @@ htmlcov/
|
||||||
_build/
|
_build/
|
||||||
dist/
|
dist/
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
.tox
|
||||||
|
|
|
||||||
13
NEWS.rst
13
NEWS.rst
|
|
@ -1,6 +1,19 @@
|
||||||
Release History
|
Release History
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
0.5.0 (2016-01-12)
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
* Updated to include all features from the Python 3.4 and 3.5 releases 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
|
||||||
|
|
||||||
|
* Python 2.6, 3.2 and 3.3 have been dropped from compatibility testing
|
||||||
|
|
||||||
|
|
||||||
0.4.0 (2012-05-05)
|
0.4.0 (2012-05-05)
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
0.4.0
|
0.5.0
|
||||||
|
|
|
||||||
227
contextlib2.py
227
contextlib2.py
|
|
@ -1,12 +1,15 @@
|
||||||
"""contextlib2 - backports and enhancements to the contextlib module"""
|
"""contextlib2 - backports and enhancements to the contextlib module"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
from collections import deque
|
from collections import deque
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
__all__ = ["contextmanager", "closing", "ContextDecorator",
|
__all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack",
|
||||||
"ContextStack", "ExitStack"]
|
"redirect_stdout", "redirect_stderr", "suppress"]
|
||||||
|
|
||||||
|
# Backwards compatibility
|
||||||
|
__all__ += ["ContextStack"]
|
||||||
|
|
||||||
class ContextDecorator(object):
|
class ContextDecorator(object):
|
||||||
"A base class or mixin that enables context managers to work as decorators."
|
"A base class or mixin that enables context managers to work as decorators."
|
||||||
|
|
@ -20,13 +23,30 @@ class ContextDecorator(object):
|
||||||
Overriding this method allows otherwise one-shot context managers
|
Overriding this method allows otherwise one-shot context managers
|
||||||
like _GeneratorContextManager to support use as decorators via
|
like _GeneratorContextManager to support use as decorators via
|
||||||
implicit recreation.
|
implicit recreation.
|
||||||
|
|
||||||
|
DEPRECATED: refresh_cm was never added to the standard library's
|
||||||
|
ContextDecorator API
|
||||||
|
"""
|
||||||
|
warnings.warn("refresh_cm was never added to the standard library",
|
||||||
|
DeprecationWarning)
|
||||||
|
return self._recreate_cm()
|
||||||
|
|
||||||
|
def _recreate_cm(self):
|
||||||
|
"""Return a recreated instance of self.
|
||||||
|
|
||||||
|
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
|
return self
|
||||||
|
|
||||||
def __call__(self, func):
|
def __call__(self, func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def inner(*args, **kwds):
|
def inner(*args, **kwds):
|
||||||
with self.refresh_cm():
|
with self._recreate_cm():
|
||||||
return func(*args, **kwds)
|
return func(*args, **kwds)
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
|
|
@ -34,15 +54,25 @@ class ContextDecorator(object):
|
||||||
class _GeneratorContextManager(ContextDecorator):
|
class _GeneratorContextManager(ContextDecorator):
|
||||||
"""Helper for @contextmanager decorator."""
|
"""Helper for @contextmanager decorator."""
|
||||||
|
|
||||||
def __init__(self, func, *args, **kwds):
|
def __init__(self, func, args, kwds):
|
||||||
self.gen = func(*args, **kwds)
|
self.gen = func(*args, **kwds)
|
||||||
self.func, self.args, self.kwds = 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):
|
def _recreate_cm(self):
|
||||||
# _GCM instances are one-shot context managers, so the
|
# _GCM instances are one-shot context managers, so the
|
||||||
# CM must be recreated each time a decorated function is
|
# CM must be recreated each time a decorated function is
|
||||||
# called
|
# called
|
||||||
return self.__class__(self.func, *self.args, **self.kwds)
|
return self.__class__(self.func, self.args, self.kwds)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -67,10 +97,17 @@ class _GeneratorContextManager(ContextDecorator):
|
||||||
self.gen.throw(type, value, traceback)
|
self.gen.throw(type, value, traceback)
|
||||||
raise RuntimeError("generator didn't stop after throw()")
|
raise RuntimeError("generator didn't stop after throw()")
|
||||||
except StopIteration as exc:
|
except StopIteration as exc:
|
||||||
# Suppress the exception *unless* it's the same exception that
|
# Suppress StopIteration *unless* it's the same exception that
|
||||||
# was passed to throw(). This prevents a StopIteration
|
# was passed to throw(). This prevents a StopIteration
|
||||||
# raised inside the "with" statement from being suppressed
|
# raised inside the "with" statement from being suppressed.
|
||||||
return exc is not value
|
return exc is not value
|
||||||
|
except RuntimeError as exc:
|
||||||
|
# Likewise, avoid suppressing if a StopIteration exception
|
||||||
|
# was passed to throw() and later wrapped into a RuntimeError
|
||||||
|
# (see PEP 479).
|
||||||
|
if _HAVE_EXCEPTION_CHAINING and exc.__cause__ is value:
|
||||||
|
return False
|
||||||
|
raise
|
||||||
except:
|
except:
|
||||||
# only re-raise if it's *not* the exception that was
|
# only re-raise if it's *not* the exception that was
|
||||||
# passed to throw(), because __exit__() must not raise
|
# passed to throw(), because __exit__() must not raise
|
||||||
|
|
@ -113,7 +150,7 @@ def contextmanager(func):
|
||||||
"""
|
"""
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def helper(*args, **kwds):
|
def helper(*args, **kwds):
|
||||||
return _GeneratorContextManager(func, *args, **kwds)
|
return _GeneratorContextManager(func, args, kwds)
|
||||||
return helper
|
return helper
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -142,6 +179,115 @@ class closing(object):
|
||||||
self.thing.close()
|
self.thing.close()
|
||||||
|
|
||||||
|
|
||||||
|
class _RedirectStream:
|
||||||
|
|
||||||
|
_stream = None
|
||||||
|
|
||||||
|
def __init__(self, new_target):
|
||||||
|
self._new_target = new_target
|
||||||
|
# We use a list of old targets to make this CM re-entrant
|
||||||
|
self._old_targets = []
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._old_targets.append(getattr(sys, self._stream))
|
||||||
|
setattr(sys, self._stream, self._new_target)
|
||||||
|
return self._new_target
|
||||||
|
|
||||||
|
def __exit__(self, exctype, excinst, exctb):
|
||||||
|
setattr(sys, self._stream, self._old_targets.pop())
|
||||||
|
|
||||||
|
|
||||||
|
class redirect_stdout(_RedirectStream):
|
||||||
|
"""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)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_stream = "stdout"
|
||||||
|
|
||||||
|
|
||||||
|
class redirect_stderr(_RedirectStream):
|
||||||
|
"""Context manager for temporarily redirecting stderr to another file."""
|
||||||
|
|
||||||
|
_stream = "stderr"
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
_HAVE_EXCEPTION_CHAINING = sys.version_info.major >= 3
|
||||||
|
if _HAVE_EXCEPTION_CHAINING:
|
||||||
|
def _make_context_fixer(frame_exc):
|
||||||
|
def _fix_exception_context(new_exc, old_exc):
|
||||||
|
# Context may not be correct, so find the end of the chain
|
||||||
|
while 1:
|
||||||
|
exc_context = new_exc.__context__
|
||||||
|
if exc_context is old_exc:
|
||||||
|
# Context is already set correctly (see issue 20317)
|
||||||
|
return
|
||||||
|
if exc_context is None or exc_context is frame_exc:
|
||||||
|
break
|
||||||
|
new_exc = exc_context
|
||||||
|
# Change the end of the chain to point to the exception
|
||||||
|
# we expect it to reference
|
||||||
|
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
|
# Inspired by discussions on http://bugs.python.org/issue13585
|
||||||
class ExitStack(object):
|
class ExitStack(object):
|
||||||
"""Context manager for dynamic management of a stack of exit callbacks
|
"""Context manager for dynamic management of a stack of exit callbacks
|
||||||
|
|
@ -152,7 +298,7 @@ class ExitStack(object):
|
||||||
files = [stack.enter_context(open(fname)) for fname in filenames]
|
files = [stack.enter_context(open(fname)) for fname in filenames]
|
||||||
# All opened files will automatically be closed at the end of
|
# All opened files will automatically be closed at the end of
|
||||||
# the with statement, even if attempts to open files later
|
# 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):
|
def __init__(self):
|
||||||
|
|
@ -177,8 +323,8 @@ class ExitStack(object):
|
||||||
|
|
||||||
Can suppress exceptions the same way __exit__ methods can.
|
Can suppress exceptions the same way __exit__ methods can.
|
||||||
|
|
||||||
Also accepts any object with an __exit__ method (registering the
|
Also accepts any object with an __exit__ method (registering a call
|
||||||
method instead of the object itself)
|
to the method instead of the object itself)
|
||||||
"""
|
"""
|
||||||
# We use an unbound method rather than a bound method to follow
|
# We use an unbound method rather than a bound method to follow
|
||||||
# the standard lookup behaviour for special methods
|
# the standard lookup behaviour for special methods
|
||||||
|
|
@ -226,40 +372,43 @@ class ExitStack(object):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *exc_details):
|
def __exit__(self, *exc_details):
|
||||||
if not self._exit_callbacks:
|
received_exc = exc_details[0] is not None
|
||||||
return
|
|
||||||
# This looks complicated, but it is really just
|
# We manipulate the exception state so it behaves as though
|
||||||
# setting up a chain of try-expect statements to ensure
|
# we were actually nesting multiple with statements
|
||||||
# that outer callbacks still get invoked even if an
|
frame_exc = sys.exc_info()[1]
|
||||||
# inner one throws an exception
|
_fix_exception_context = _make_context_fixer(frame_exc)
|
||||||
def _invoke_next_callback(exc_details):
|
|
||||||
# Callbacks are removed from the list in FIFO order
|
# Callbacks are invoked in LIFO order to match the behaviour of
|
||||||
# but the recursion means they're invoked in LIFO order
|
# nested context managers
|
||||||
cb = self._exit_callbacks.popleft()
|
suppressed_exc = False
|
||||||
if not self._exit_callbacks:
|
pending_raise = False
|
||||||
# Innermost callback is invoked directly
|
while self._exit_callbacks:
|
||||||
return cb(*exc_details)
|
cb = self._exit_callbacks.pop()
|
||||||
# More callbacks left, so descend another level in the stack
|
|
||||||
try:
|
try:
|
||||||
suppress_exc = _invoke_next_callback(exc_details)
|
if cb(*exc_details):
|
||||||
except:
|
suppressed_exc = True
|
||||||
suppress_exc = cb(*sys.exc_info())
|
pending_raise = False
|
||||||
# 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:
|
|
||||||
exc_details = (None, None, None)
|
exc_details = (None, None, None)
|
||||||
suppress_exc = cb(*exc_details) or suppress_exc
|
except:
|
||||||
return suppress_exc
|
new_exc_details = sys.exc_info()
|
||||||
# Kick off the recursive chain
|
# simulate the stack of exceptions by setting the context
|
||||||
return _invoke_next_callback(exc_details)
|
_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
|
# Preserve backwards compatibility
|
||||||
class ContextStack(ExitStack):
|
class ContextStack(ExitStack):
|
||||||
"""Backwards compatibility alias for ExitStack"""
|
"""Backwards compatibility alias for ExitStack"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
warnings.warn("ContextStack has been renamed to ExitStack",
|
||||||
|
DeprecationWarning)
|
||||||
|
super(ContextStack, self).__init__()
|
||||||
|
|
||||||
def register_exit(self, callback):
|
def register_exit(self, callback):
|
||||||
return self.push(callback)
|
return self.push(callback)
|
||||||
|
|
||||||
|
|
|
||||||
432
docs/index.rst
432
docs/index.rst
|
|
@ -21,22 +21,25 @@ involving the ``with`` statement.
|
||||||
Additions Relative to the Standard Library
|
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.5 version of
|
||||||
:mod:`contextlib` to earlier releases. However, it is also a proving ground
|
: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
|
for new features not yet part of the standard library.
|
||||||
are currently:
|
|
||||||
|
|
||||||
* :class:`ExitStack`
|
There are currently no such features in the module.
|
||||||
* :meth:`ContextDecorator.refresh_cm`
|
|
||||||
|
Refer to the :mod:`contextlib` documentation for details of which
|
||||||
|
versions of Python 3 introduce the various APIs provided in this module.
|
||||||
|
|
||||||
|
|
||||||
API Reference
|
API Reference
|
||||||
=============
|
=============
|
||||||
|
|
||||||
.. function:: contextmanager
|
Functions and classes provided:
|
||||||
|
|
||||||
This function is a decorator that can be used to define a factory
|
.. decorator:: contextmanager
|
||||||
function for ``with`` statement context managers, without needing to
|
|
||||||
|
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.
|
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!)::
|
A simple example (this is not recommended as a real way of generating HTML!)::
|
||||||
|
|
@ -56,24 +59,24 @@ API Reference
|
||||||
foo
|
foo
|
||||||
</h1>
|
</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
|
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.
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
: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
|
When used as a decorator, a new generator instance is implicitly created on
|
||||||
each function call (this allows the otherwise "one-shot" context managers
|
each function call (this allows the otherwise "one-shot" context managers
|
||||||
created by :func:`contextmanager` to meet the requirement that context
|
created by :func:`contextmanager` to meet the requirement that context
|
||||||
|
|
@ -104,7 +107,97 @@ API Reference
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
without needing to explicitly close ``page``. Even if an error occurs,
|
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:`reentrant <reentrant-cms>`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
Part of the standard library in Python 3.4 and later
|
||||||
|
|
||||||
|
|
||||||
|
.. function:: redirect_stderr(new_target)
|
||||||
|
|
||||||
|
Similar to :func:`redirect_stdout`, but redirecting :data:`sys.stderr` to
|
||||||
|
another file or file-like object.
|
||||||
|
|
||||||
|
This context manager is :ref:`reentrant <reentrant-cms>`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.5
|
||||||
|
Part of the standard library in Python 3.5 and later
|
||||||
|
|
||||||
|
|
||||||
.. class:: ContextDecorator()
|
.. class:: ContextDecorator()
|
||||||
|
|
@ -112,7 +205,7 @@ API Reference
|
||||||
A base class that enables a context manager to also be used as a decorator.
|
A base class that enables a context manager to also be used as a decorator.
|
||||||
|
|
||||||
Context managers inheriting from ``ContextDecorator`` have to implement
|
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.
|
exception handling even when used as a decorator.
|
||||||
|
|
||||||
``ContextDecorator`` is used by :func:`contextmanager`, so you get this
|
``ContextDecorator`` is used by :func:`contextmanager`, so you get this
|
||||||
|
|
@ -174,22 +267,11 @@ API Reference
|
||||||
def __exit__(self, *exc):
|
def __exit__(self, *exc):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
.. method:: refresh_cm()
|
.. note::
|
||||||
|
|
||||||
This method is invoked each time a call is made to a decorated function.
|
|
||||||
The default implementation just returns *self*.
|
|
||||||
|
|
||||||
As the decorated function must be able to be called multiple times, the
|
As the decorated function must be able to be called multiple times, the
|
||||||
underlying context manager must normally support use in multiple
|
underlying context manager must support use in multiple :keyword:`with`
|
||||||
``with`` statements (preferably in a thread-safe manner). If
|
statements. If this is not the case, then the original construct with the
|
||||||
this is not the case, then the context manager must define this method
|
explicit :keyword:`with` statement inside the function should be used.
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
.. class:: ExitStack()
|
.. class:: ExitStack()
|
||||||
|
|
@ -205,20 +287,32 @@ API Reference
|
||||||
files = [stack.enter_context(open(fname)) for fname in filenames]
|
files = [stack.enter_context(open(fname)) for fname in filenames]
|
||||||
# All opened files will automatically be closed at the end of
|
# All opened files will automatically be closed at the end of
|
||||||
# the with statement, even if attempts to open files later
|
# 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
|
Each instance maintains a stack of registered callbacks that are called in
|
||||||
reverse order when the instance is closed (either explicitly or implicitly
|
reverse order when the instance is closed (either explicitly or implicitly
|
||||||
at the end of a ``with`` statement). Note that callbacks are *not* invoked
|
at the end of a :keyword:`with` statement). Note that callbacks are *not*
|
||||||
implicitly when the context stack instance is garbage collected.
|
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
|
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
|
statements had been used with the registered set of callbacks. This even
|
||||||
extends to exception handling - if an inner callback suppresses or replaces
|
extends to exception handling - if an inner callback suppresses or replaces
|
||||||
an exception, then outer callbacks will be passed arguments based on that
|
an exception, then outer callbacks will be passed arguments based on that
|
||||||
updated state.
|
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)
|
.. method:: enter_context(cm)
|
||||||
|
|
||||||
Enters a new context manager and adds its :meth:`__exit__` method to
|
Enters a new context manager and adds its :meth:`__exit__` method to
|
||||||
|
|
@ -226,21 +320,25 @@ API Reference
|
||||||
manager's own :meth:`__enter__` method.
|
manager's own :meth:`__enter__` method.
|
||||||
|
|
||||||
These context managers may suppress exceptions just as they normally
|
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)
|
.. method:: push(exit)
|
||||||
|
|
||||||
Directly accepts a callback with the same signature as a
|
Adds a context manager's :meth:`__exit__` method to the callback stack.
|
||||||
context manager's :meth:`__exit__` method and adds it 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
|
By returning true values, these callbacks can suppress exceptions the
|
||||||
same way context manager :meth:`__exit__` methods can.
|
same way context manager :meth:`__exit__` methods can.
|
||||||
|
|
||||||
This method also accepts any object with an ``__exit__`` method, and
|
The passed in object is returned from the function, allowing this
|
||||||
will register that method as the callback. This is mainly useful to
|
method to be used as a function decorator.
|
||||||
cover part of an :meth:`__enter__` implementation with a context
|
|
||||||
manager's own :meth:`__exit__` method.
|
|
||||||
|
|
||||||
.. method:: callback(callback, *args, **kwds)
|
.. method:: callback(callback, *args, **kwds)
|
||||||
|
|
||||||
|
|
@ -250,22 +348,27 @@ API Reference
|
||||||
Unlike the other methods, callbacks added this way cannot suppress
|
Unlike the other methods, callbacks added this way cannot suppress
|
||||||
exceptions (as they are never passed the exception details).
|
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()
|
.. method:: pop_all()
|
||||||
|
|
||||||
Transfers the callback stack to a fresh instance and returns it. No
|
Transfers the callback stack to a fresh :class:`ExitStack` instance
|
||||||
callbacks are invoked by this operation - instead, they will now be
|
and returns it. No callbacks are invoked by this operation - instead,
|
||||||
invoked when the new stack is closed (either explicitly or implicitly).
|
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"
|
For example, a group of files can be opened as an "all or nothing"
|
||||||
operation as follows::
|
operation as follows::
|
||||||
|
|
||||||
with ExitStack() as stack:
|
with ExitStack() as stack:
|
||||||
files = [stack.enter_context(open(fname)) for fname in filenames]
|
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
|
close_files = stack.pop_all().close
|
||||||
# If opening any file fails, all previously opened files will be
|
# If opening any file fails, all previously opened files will be
|
||||||
# closed automatically. If all files are opened successfully,
|
# closed automatically. If all files are opened successfully,
|
||||||
# they will remain open even after the with statement ends.
|
# 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()
|
.. method:: close()
|
||||||
|
|
||||||
|
|
@ -274,21 +377,6 @@ API Reference
|
||||||
callbacks registered, the arguments passed in will indicate that no
|
callbacks registered, the arguments passed in will indicate that no
|
||||||
exception occurred.
|
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
|
Examples and Recipes
|
||||||
====================
|
====================
|
||||||
|
|
@ -299,53 +387,6 @@ the tools provided by :mod:`contextlib2`. Some of them may also work with
|
||||||
case, it is noted at the end of the example.
|
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
|
Cleaning up in an ``__enter__`` implementation
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
|
@ -384,6 +425,8 @@ and maps them to the context management protocol::
|
||||||
# We don't need to duplicate any of our resource release logic
|
# We don't need to duplicate any of our resource release logic
|
||||||
self.release_resource()
|
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
|
Replacing any use of ``try-finally`` and flag variables
|
||||||
-------------------------------------------------------
|
-------------------------------------------------------
|
||||||
|
|
@ -456,7 +499,174 @@ advance::
|
||||||
|
|
||||||
Due to the way the decorator protocol works, a callback function
|
Due to the way the decorator protocol works, a callback function
|
||||||
declared this way cannot take any parameters. Instead, any resources to
|
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
|
Obtaining the Module
|
||||||
|
|
|
||||||
19
setup.py
19
setup.py
|
|
@ -1,9 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
# Technically, unittest2 is a dependency to run the tests on 2.6 and 3.1
|
# Technically, unittest2 is a dependency to run the tests on 2.7
|
||||||
# This file ignores that, since I don't want to depend on distribute
|
# This file ignores that, since I don't want to depend on
|
||||||
# or setuptools just to get "tests_require" support
|
# setuptools just to get "tests_require" support
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='contextlib2',
|
name='contextlib2',
|
||||||
|
|
@ -14,5 +14,16 @@ setup(
|
||||||
long_description=open('README.md').read(),
|
long_description=open('README.md').read(),
|
||||||
author='Nick Coghlan',
|
author='Nick Coghlan',
|
||||||
author_email='ncoghlan@gmail.com',
|
author_email='ncoghlan@gmail.com',
|
||||||
url='http://contextlib2.readthedocs.org'
|
url='http://contextlib2.readthedocs.org',
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 5 - Production/Stable',
|
||||||
|
'License :: OSI Approved :: Python Software Foundation License',
|
||||||
|
# These are the Python versions tested, it may work on others
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.4',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
],
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
@ -1,14 +1,19 @@
|
||||||
#!/usr/bin/env python
|
"""Unit tests for contextlib2.py"""
|
||||||
"""Unit tests for contextlib2"""
|
from __future__ import print_function
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import io
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
if not hasattr(unittest, "skipIf"):
|
import __future__ # For PEP 479 conditional test
|
||||||
import unittest2 as unittest
|
import contextlib2
|
||||||
|
|
||||||
from contextlib2 import * # Tests __all__
|
from contextlib2 import * # Tests __all__
|
||||||
|
|
||||||
|
if not hasattr(unittest.TestCase, "assertRaisesRegex"):
|
||||||
|
import unittest2 as unittest
|
||||||
|
|
||||||
|
requires_docstrings = unittest.skipIf(sys.flags.optimize >= 2,
|
||||||
|
"Test requires docstrings")
|
||||||
|
|
||||||
class ContextManagerTestCase(unittest.TestCase):
|
class ContextManagerTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -81,6 +86,44 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
raise ZeroDivisionError(999)
|
raise ZeroDivisionError(999)
|
||||||
self.assertEqual(state, [1, 42, 999])
|
self.assertEqual(state, [1, 42, 999])
|
||||||
|
|
||||||
|
def test_contextmanager_except_stopiter(self):
|
||||||
|
stop_exc = StopIteration('spam')
|
||||||
|
@contextmanager
|
||||||
|
def woohoo():
|
||||||
|
yield
|
||||||
|
try:
|
||||||
|
with self.assertWarnsRegex(PendingDeprecationWarning,
|
||||||
|
"StopIteration"):
|
||||||
|
with woohoo():
|
||||||
|
raise stop_exc
|
||||||
|
except Exception as ex:
|
||||||
|
self.assertIs(ex, stop_exc)
|
||||||
|
else:
|
||||||
|
self.fail('StopIteration was suppressed')
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(__future__, "generator_stop"),
|
||||||
|
"Test only valid for versions implementing PEP 479")
|
||||||
|
def test_contextmanager_except_pep479(self):
|
||||||
|
code = """\
|
||||||
|
from __future__ import generator_stop
|
||||||
|
from contextlib import contextmanager
|
||||||
|
@contextmanager
|
||||||
|
def woohoo():
|
||||||
|
yield
|
||||||
|
"""
|
||||||
|
locals = {}
|
||||||
|
exec(code, locals, locals)
|
||||||
|
woohoo = locals['woohoo']
|
||||||
|
|
||||||
|
stop_exc = StopIteration('spam')
|
||||||
|
try:
|
||||||
|
with woohoo():
|
||||||
|
raise stop_exc
|
||||||
|
except Exception as ex:
|
||||||
|
self.assertIs(ex, stop_exc)
|
||||||
|
else:
|
||||||
|
self.fail('StopIteration was suppressed')
|
||||||
|
|
||||||
def _create_contextmanager_attribs(self):
|
def _create_contextmanager_attribs(self):
|
||||||
def attribs(**kw):
|
def attribs(**kw):
|
||||||
def decorate(func):
|
def decorate(func):
|
||||||
|
|
@ -99,15 +142,33 @@ class ContextManagerTestCase(unittest.TestCase):
|
||||||
self.assertEqual(baz.__name__,'baz')
|
self.assertEqual(baz.__name__,'baz')
|
||||||
self.assertEqual(baz.foo, 'bar')
|
self.assertEqual(baz.foo, 'bar')
|
||||||
|
|
||||||
@unittest.skipIf(sys.flags.optimize >= 2,
|
@requires_docstrings
|
||||||
"Docstrings are omitted with -O2 and above")
|
|
||||||
def test_contextmanager_doc_attrib(self):
|
def test_contextmanager_doc_attrib(self):
|
||||||
baz = self._create_contextmanager_attribs()
|
baz = self._create_contextmanager_attribs()
|
||||||
self.assertEqual(baz.__doc__, "Whee!")
|
self.assertEqual(baz.__doc__, "Whee!")
|
||||||
|
|
||||||
|
@requires_docstrings
|
||||||
|
def test_instance_docstring_given_cm_docstring(self):
|
||||||
|
baz = self._create_contextmanager_attribs()(None)
|
||||||
|
self.assertEqual(baz.__doc__, "Whee!")
|
||||||
|
|
||||||
|
def test_keywords(self):
|
||||||
|
# Ensure no keyword arguments are inhibited
|
||||||
|
@contextmanager
|
||||||
|
def woohoo(self, func, args, kwds):
|
||||||
|
yield (self, func, args, kwds)
|
||||||
|
with woohoo(self=11, func=22, args=33, kwds=44) as target:
|
||||||
|
self.assertEqual(target, (11, 22, 33, 44))
|
||||||
|
|
||||||
|
|
||||||
class ClosingTestCase(unittest.TestCase):
|
class ClosingTestCase(unittest.TestCase):
|
||||||
|
|
||||||
# XXX This needs more work
|
@requires_docstrings
|
||||||
|
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):
|
def test_closing(self):
|
||||||
state = []
|
state = []
|
||||||
|
|
@ -135,6 +196,7 @@ class ClosingTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class mycontext(ContextDecorator):
|
class mycontext(ContextDecorator):
|
||||||
|
"""Example decoration-compatible context manager for testing"""
|
||||||
started = False
|
started = False
|
||||||
exc = None
|
exc = None
|
||||||
catch = False
|
catch = False
|
||||||
|
|
@ -150,6 +212,13 @@ class mycontext(ContextDecorator):
|
||||||
|
|
||||||
class TestContextDecorator(unittest.TestCase):
|
class TestContextDecorator(unittest.TestCase):
|
||||||
|
|
||||||
|
@requires_docstrings
|
||||||
|
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):
|
def test_contextdecorator(self):
|
||||||
context = mycontext()
|
context = mycontext()
|
||||||
with context as result:
|
with context as result:
|
||||||
|
|
@ -162,12 +231,11 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
def test_contextdecorator_with_exception(self):
|
def test_contextdecorator_with_exception(self):
|
||||||
context = mycontext()
|
context = mycontext()
|
||||||
|
|
||||||
with self.assertRaises(NameError):
|
with self.assertRaisesRegex(NameError, 'foo'):
|
||||||
with context:
|
with context:
|
||||||
raise NameError('foo')
|
raise NameError('foo')
|
||||||
self.assertIsNotNone(context.exc)
|
self.assertIsNotNone(context.exc)
|
||||||
self.assertIs(context.exc[0], NameError)
|
self.assertIs(context.exc[0], NameError)
|
||||||
self.assertIn('foo', str(context.exc[1]))
|
|
||||||
|
|
||||||
context = mycontext()
|
context = mycontext()
|
||||||
context.catch = True
|
context.catch = True
|
||||||
|
|
@ -197,11 +265,10 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
self.assertTrue(context.started)
|
self.assertTrue(context.started)
|
||||||
raise NameError('foo')
|
raise NameError('foo')
|
||||||
|
|
||||||
with self.assertRaises(NameError):
|
with self.assertRaisesRegex(NameError, 'foo'):
|
||||||
test()
|
test()
|
||||||
self.assertIsNotNone(context.exc)
|
self.assertIsNotNone(context.exc)
|
||||||
self.assertIs(context.exc[0], NameError)
|
self.assertIs(context.exc[0], NameError)
|
||||||
self.assertIn('foo', str(context.exc[1]))
|
|
||||||
|
|
||||||
|
|
||||||
def test_decorating_method(self):
|
def test_decorating_method(self):
|
||||||
|
|
@ -302,9 +369,18 @@ class TestContextDecorator(unittest.TestCase):
|
||||||
test('something else')
|
test('something else')
|
||||||
self.assertEqual(state, [1, 'something else', 999])
|
self.assertEqual(state, [1, 'something else', 999])
|
||||||
|
|
||||||
|
# Detailed exception chaining checks only make sense on Python 3
|
||||||
|
check_exception_chaining = contextlib2._HAVE_EXCEPTION_CHAINING
|
||||||
|
|
||||||
class TestExitStack(unittest.TestCase):
|
class TestExitStack(unittest.TestCase):
|
||||||
|
|
||||||
|
@requires_docstrings
|
||||||
|
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):
|
def test_no_resources(self):
|
||||||
with ExitStack():
|
with ExitStack():
|
||||||
pass
|
pass
|
||||||
|
|
@ -416,6 +492,211 @@ class TestExitStack(unittest.TestCase):
|
||||||
new_stack.close()
|
new_stack.close()
|
||||||
self.assertEqual(result, [1, 2, 3])
|
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
|
||||||
|
class RaiseExc:
|
||||||
|
def __init__(self, exc):
|
||||||
|
self.exc = exc
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
def __exit__(self, *exc_details):
|
||||||
|
raise self.exc
|
||||||
|
|
||||||
|
class RaiseExcWithContext:
|
||||||
|
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:
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
def __exit__(self, *exc_details):
|
||||||
|
self.__class__.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
|
||||||
|
def raise_exc(exc):
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
saved_details = [None]
|
||||||
|
def suppress_exc(*exc_details):
|
||||||
|
saved_details[0] = 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_exit_exception_with_correct_context(self):
|
||||||
|
# http://bugs.python.org/issue20317
|
||||||
|
@contextmanager
|
||||||
|
def gets_the_context_right(exc):
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
raise exc
|
||||||
|
|
||||||
|
exc1 = Exception(1)
|
||||||
|
exc2 = Exception(2)
|
||||||
|
exc3 = Exception(3)
|
||||||
|
exc4 = Exception(4)
|
||||||
|
|
||||||
|
# The contextmanager already fixes the context, so prior to the
|
||||||
|
# fix, ExitStack would try to fix it *again* and get into an
|
||||||
|
# infinite self-referential loop
|
||||||
|
try:
|
||||||
|
with ExitStack() as stack:
|
||||||
|
stack.enter_context(gets_the_context_right(exc4))
|
||||||
|
stack.enter_context(gets_the_context_right(exc3))
|
||||||
|
stack.enter_context(gets_the_context_right(exc2))
|
||||||
|
raise exc1
|
||||||
|
except Exception as exc:
|
||||||
|
self.assertIs(exc, exc4)
|
||||||
|
if check_exception_chaining:
|
||||||
|
self.assertIs(exc.__context__, exc3)
|
||||||
|
self.assertIs(exc.__context__.__context__, exc2)
|
||||||
|
self.assertIs(exc.__context__.__context__.__context__, exc1)
|
||||||
|
self.assertIsNone(
|
||||||
|
exc.__context__.__context__.__context__.__context__)
|
||||||
|
|
||||||
|
def test_exit_exception_with_existing_context(self):
|
||||||
|
# Addresses a lack of test coverage discovered after checking in a
|
||||||
|
# fix for issue 20317 that still contained debugging code.
|
||||||
|
def raise_nested(inner_exc, outer_exc):
|
||||||
|
try:
|
||||||
|
raise inner_exc
|
||||||
|
finally:
|
||||||
|
raise outer_exc
|
||||||
|
exc1 = Exception(1)
|
||||||
|
exc2 = Exception(2)
|
||||||
|
exc3 = Exception(3)
|
||||||
|
exc4 = Exception(4)
|
||||||
|
exc5 = Exception(5)
|
||||||
|
try:
|
||||||
|
with ExitStack() as stack:
|
||||||
|
stack.callback(raise_nested, exc4, exc5)
|
||||||
|
stack.callback(raise_nested, exc2, exc3)
|
||||||
|
raise exc1
|
||||||
|
except Exception as exc:
|
||||||
|
self.assertIs(exc, exc5)
|
||||||
|
if check_exception_chaining:
|
||||||
|
self.assertIs(exc.__context__, exc4)
|
||||||
|
self.assertIs(exc.__context__.__context__, exc3)
|
||||||
|
self.assertIs(exc.__context__.__context__.__context__, exc2)
|
||||||
|
self.assertIs(
|
||||||
|
exc.__context__.__context__.__context__.__context__, exc1)
|
||||||
|
self.assertIsNone(
|
||||||
|
exc.__context__.__context__.__context__.__context__.__context__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
def test_instance_bypass(self):
|
||||||
class Example(object): pass
|
class Example(object): pass
|
||||||
cm = Example()
|
cm = Example()
|
||||||
|
|
@ -426,128 +707,129 @@ class TestExitStack(unittest.TestCase):
|
||||||
self.assertIs(stack._exit_callbacks[-1], cm)
|
self.assertIs(stack._exit_callbacks[-1], cm)
|
||||||
|
|
||||||
|
|
||||||
class TestContextStack(unittest.TestCase):
|
class TestRedirectStream:
|
||||||
|
|
||||||
def test_no_resources(self):
|
redirect_stream = None
|
||||||
with ContextStack():
|
orig_stream = None
|
||||||
pass
|
|
||||||
|
|
||||||
def test_register(self):
|
@requires_docstrings
|
||||||
expected = [
|
def test_instance_docs(self):
|
||||||
((), {}),
|
# Issue 19330: ensure context manager instances have good docstrings
|
||||||
((1,), {}),
|
cm_docstring = self.redirect_stream.__doc__
|
||||||
((1,2), {}),
|
obj = self.redirect_stream(None)
|
||||||
((), dict(example=1)),
|
self.assertEqual(obj.__doc__, cm_docstring)
|
||||||
((1,), dict(example=1)),
|
|
||||||
((1,2), dict(example=1)),
|
|
||||||
]
|
|
||||||
result = []
|
|
||||||
def _exit(*args, **kwds):
|
|
||||||
"""Test metadata propagation"""
|
|
||||||
result.append((args, kwds))
|
|
||||||
with ContextStack() as stack:
|
|
||||||
for args, kwds in reversed(expected):
|
|
||||||
if args and kwds:
|
|
||||||
f = stack.register(_exit, *args, **kwds)
|
|
||||||
elif args:
|
|
||||||
f = stack.register(_exit, *args)
|
|
||||||
elif kwds:
|
|
||||||
f = stack.register(_exit, **kwds)
|
|
||||||
else:
|
|
||||||
f = stack.register(_exit)
|
|
||||||
self.assertIs(f, _exit)
|
|
||||||
for wrapper in stack._exit_callbacks:
|
|
||||||
self.assertIs(wrapper.__wrapped__, _exit)
|
|
||||||
self.assertNotEqual(wrapper.__name__, _exit.__name__)
|
|
||||||
self.assertIsNone(wrapper.__doc__, _exit.__doc__)
|
|
||||||
self.assertEqual(result, expected)
|
|
||||||
|
|
||||||
def test_register_exit(self):
|
def test_no_redirect_in_init(self):
|
||||||
exc_raised = ZeroDivisionError
|
orig_stdout = getattr(sys, self.orig_stream)
|
||||||
def _expect_exc(exc_type, exc, exc_tb):
|
self.redirect_stream(None)
|
||||||
self.assertIs(exc_type, exc_raised)
|
self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
|
||||||
def _suppress_exc(*exc_details):
|
|
||||||
return True
|
def test_redirect_to_string_io(self):
|
||||||
def _expect_ok(exc_type, exc, exc_tb):
|
f = io.StringIO()
|
||||||
self.assertIsNone(exc_type)
|
msg = "Consider an API like help(), which prints directly to stdout"
|
||||||
self.assertIsNone(exc)
|
orig_stdout = getattr(sys, self.orig_stream)
|
||||||
self.assertIsNone(exc_tb)
|
with self.redirect_stream(f):
|
||||||
class ExitCM(object):
|
print(msg, file=getattr(sys, self.orig_stream))
|
||||||
def __init__(self, check_exc):
|
self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
|
||||||
self.check_exc = check_exc
|
s = f.getvalue().strip()
|
||||||
def __enter__(self):
|
self.assertEqual(s, msg)
|
||||||
self.fail("Should not be called!")
|
|
||||||
def __exit__(self, *exc_details):
|
def test_enter_result_is_target(self):
|
||||||
self.check_exc(*exc_details)
|
f = io.StringIO()
|
||||||
with ContextStack() as stack:
|
with self.redirect_stream(f) as enter_result:
|
||||||
stack.register_exit(_expect_ok)
|
self.assertIs(enter_result, f)
|
||||||
self.assertIs(stack._exit_callbacks[-1], _expect_ok)
|
|
||||||
cm = ExitCM(_expect_ok)
|
def test_cm_is_reusable(self):
|
||||||
stack.register_exit(cm)
|
f = io.StringIO()
|
||||||
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
|
write_to_f = self.redirect_stream(f)
|
||||||
stack.register_exit(_suppress_exc)
|
orig_stdout = getattr(sys, self.orig_stream)
|
||||||
self.assertIs(stack._exit_callbacks[-1], _suppress_exc)
|
with write_to_f:
|
||||||
cm = ExitCM(_expect_exc)
|
print("Hello", end=" ", file=getattr(sys, self.orig_stream))
|
||||||
stack.register_exit(cm)
|
with write_to_f:
|
||||||
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
|
print("World!", file=getattr(sys, self.orig_stream))
|
||||||
stack.register_exit(_expect_exc)
|
self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
|
||||||
self.assertIs(stack._exit_callbacks[-1], _expect_exc)
|
s = f.getvalue()
|
||||||
stack.register_exit(_expect_exc)
|
self.assertEqual(s, "Hello World!\n")
|
||||||
self.assertIs(stack._exit_callbacks[-1], _expect_exc)
|
|
||||||
|
def test_cm_is_reentrant(self):
|
||||||
|
f = io.StringIO()
|
||||||
|
write_to_f = self.redirect_stream(f)
|
||||||
|
orig_stdout = getattr(sys, self.orig_stream)
|
||||||
|
with write_to_f:
|
||||||
|
print("Hello", end=" ", file=getattr(sys, self.orig_stream))
|
||||||
|
with write_to_f:
|
||||||
|
print("World!", file=getattr(sys, self.orig_stream))
|
||||||
|
self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
|
||||||
|
s = f.getvalue()
|
||||||
|
self.assertEqual(s, "Hello World!\n")
|
||||||
|
|
||||||
|
|
||||||
|
class TestRedirectStdout(TestRedirectStream, unittest.TestCase):
|
||||||
|
|
||||||
|
redirect_stream = redirect_stdout
|
||||||
|
orig_stream = "stdout"
|
||||||
|
|
||||||
|
|
||||||
|
class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
|
||||||
|
|
||||||
|
redirect_stream = redirect_stderr
|
||||||
|
orig_stream = "stderr"
|
||||||
|
|
||||||
|
|
||||||
|
class TestSuppress(unittest.TestCase):
|
||||||
|
|
||||||
|
@requires_docstrings
|
||||||
|
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
|
1/0
|
||||||
|
with suppress(ZeroDivisionError, TypeError):
|
||||||
|
len(5)
|
||||||
|
|
||||||
def test_enter_context(self):
|
def test_cm_is_reentrant(self):
|
||||||
class TestCM(object):
|
ignore_exceptions = suppress(Exception)
|
||||||
def __enter__(self):
|
with ignore_exceptions:
|
||||||
result.append(1)
|
pass
|
||||||
def __exit__(self, *exc_details):
|
with ignore_exceptions:
|
||||||
result.append(3)
|
len(5)
|
||||||
|
with ignore_exceptions:
|
||||||
result = []
|
with ignore_exceptions: # Check nested usage
|
||||||
cm = TestCM()
|
len(5)
|
||||||
with ContextStack() as stack:
|
outer_continued = True
|
||||||
@stack.register # Registered first => cleaned up last
|
1/0
|
||||||
def _exit():
|
self.assertTrue(outer_continued)
|
||||||
result.append(4)
|
|
||||||
self.assertIsNotNone(_exit)
|
|
||||||
stack.enter_context(cm)
|
|
||||||
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
|
|
||||||
result.append(2)
|
|
||||||
self.assertEqual(result, [1, 2, 3, 4])
|
|
||||||
|
|
||||||
def test_close(self):
|
|
||||||
result = []
|
|
||||||
with ContextStack() as stack:
|
|
||||||
@stack.register
|
|
||||||
def _exit():
|
|
||||||
result.append(1)
|
|
||||||
self.assertIsNotNone(_exit)
|
|
||||||
stack.close()
|
|
||||||
result.append(2)
|
|
||||||
self.assertEqual(result, [1, 2])
|
|
||||||
|
|
||||||
def test_preserve(self):
|
|
||||||
result = []
|
|
||||||
with ContextStack() as stack:
|
|
||||||
@stack.register
|
|
||||||
def _exit():
|
|
||||||
result.append(3)
|
|
||||||
self.assertIsNotNone(_exit)
|
|
||||||
new_stack = stack.preserve()
|
|
||||||
result.append(1)
|
|
||||||
result.append(2)
|
|
||||||
new_stack.close()
|
|
||||||
self.assertEqual(result, [1, 2, 3])
|
|
||||||
|
|
||||||
def test_instance_bypass(self):
|
|
||||||
class Example(object): pass
|
|
||||||
cm = Example()
|
|
||||||
cm.__exit__ = object()
|
|
||||||
stack = ContextStack()
|
|
||||||
self.assertRaises(AttributeError, stack.enter_context, cm)
|
|
||||||
stack.register_exit(cm)
|
|
||||||
self.assertIs(stack._exit_callbacks[-1], cm)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import unittest
|
import unittest
|
||||||
unittest.main(__name__)
|
unittest.main()
|
||||||
|
|
|
||||||
5
tox.ini
5
tox.ini
|
|
@ -4,3 +4,8 @@ envlist = py27, pypy, py34, py35, pypy3
|
||||||
[testenv]
|
[testenv]
|
||||||
commands = {envpython} test_contextlib2.py
|
commands = {envpython} test_contextlib2.py
|
||||||
|
|
||||||
|
[testenv:py27]
|
||||||
|
deps = unittest2
|
||||||
|
|
||||||
|
[testenv:pypy]
|
||||||
|
deps = unittest2
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue