Issue #11647: allow contextmanager objects to be used as decorators as described in the docs. Initial patch by Ysj Ray.

This commit is contained in:
Nick Coghlan 2011-05-05 23:49:25 +10:00
parent f77b74dd1b
commit 0ded3e307b
6 changed files with 50 additions and 10 deletions

View File

@ -54,8 +54,12 @@ Functions provided:
the exception has been handled, and execution will resume with the statement
immediately following the :keyword:`with` statement.
contextmanager uses :class:`ContextDecorator` so the context managers it
creates can be used as decorators as well as in :keyword:`with` statements.
:func:`contextmanager` uses :class:`ContextDecorator` so the context managers
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
managers support multiple invocations in order to be used as decorators).
.. versionchanged:: 3.2
Use of :class:`ContextDecorator`.
@ -155,6 +159,12 @@ Functions provided:
def __exit__(self, *exc):
return False
.. note::
As the decorated function must be able to be called multiple times, the
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.
.. versionadded:: 3.2

View File

@ -9,10 +9,23 @@ __all__ = ["contextmanager", "closing", "ContextDecorator"]
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 otherwise one-shot context managers like
_GeneratorContextManager to support use as
decorators via implicit recreation.
Note: this is a private interface just for _GCM in 3.2 but will be
renamed and documented for third party use in 3.3
"""
return self
def __call__(self, func):
@wraps(func)
def inner(*args, **kwds):
with self:
with self._recreate_cm():
return func(*args, **kwds)
return inner
@ -20,8 +33,15 @@ class ContextDecorator(object):
class _GeneratorContextManager(ContextDecorator):
"""Helper for @contextmanager decorator."""
def __init__(self, gen):
self.gen = gen
def __init__(self, func, *args, **kwds):
self.gen = func(*args, **kwds)
self.func, self.args, self.kwds = func, args, kwds
def _recreate_cm(self):
# _GCM instances are one-shot context managers, so the
# CM must be recreated each time a decorated function is
# called
return self.__class__(self.func, *self.args, **self.kwds)
def __enter__(self):
try:
@ -92,7 +112,7 @@ def contextmanager(func):
"""
@wraps(func)
def helper(*args, **kwds):
return _GeneratorContextManager(func(*args, **kwds))
return _GeneratorContextManager(func, *args, **kwds)
return helper

View File

@ -350,13 +350,13 @@ class TestContextDecorator(unittest.TestCase):
def test_contextmanager_as_decorator(self):
state = []
@contextmanager
def woohoo(y):
state.append(y)
yield
state.append(999)
state = []
@woohoo(1)
def test(x):
self.assertEqual(state, [1])
@ -364,6 +364,11 @@ class TestContextDecorator(unittest.TestCase):
test('something')
self.assertEqual(state, [1, 'something', 999])
# Issue #11647: Ensure the decorated function is 'reusable'
state = []
test('something else')
self.assertEqual(state, [1, 'something else', 999])
# This is needed to make the test actually run under regrtest.py!
def test_main():

View File

@ -14,8 +14,8 @@ from test.support import run_unittest
class MockContextManager(_GeneratorContextManager):
def __init__(self, gen):
_GeneratorContextManager.__init__(self, gen)
def __init__(self, func, *args, **kwds):
super().__init__(func, *args, **kwds)
self.enter_called = False
self.exit_called = False
self.exit_args = None
@ -33,7 +33,7 @@ class MockContextManager(_GeneratorContextManager):
def mock_contextmanager(func):
def helper(*args, **kwds):
return MockContextManager(func(*args, **kwds))
return MockContextManager(func, *args, **kwds)
return helper

View File

@ -704,6 +704,7 @@ Burton Radons
Brodie Rao
Antti Rasinen
Sridhar Ratnakumar
Ysj Ray
Eric Raymond
Edward K. Ream
Chris Rebert

View File

@ -83,6 +83,10 @@ Core and Builtins
Library
-------
- Issue #11647: objects created using contextlib.contextmanager now support
more than one call to the function when used as a decorator. Initial patch
by Ysj Ray.
- logging: don't define QueueListener if Python has no thread support.
- functools.cmp_to_key() now works with collections.Hashable().