mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-16 21:50:24 +00:00
Issue #1: Add ContextStack.preserve()
This commit is contained in:
parent
7f99236d3b
commit
8e373d228e
4 changed files with 48 additions and 8 deletions
2
NEWS.rst
2
NEWS.rst
|
|
@ -5,6 +5,8 @@ Release History
|
|||
0.3 (2012-01-XX)
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
* Issue #1: Add ContextStack.preserve() to move all registered callbacks to
|
||||
a new ContextStack object
|
||||
* Wrapped callbacks now use functools.wraps to aid in introspection
|
||||
* Moved version number to a VERSION.txt file (read by both docs and setup.py)
|
||||
* Added NEWS.rst (and incorporated into documentation)
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ class closing(object):
|
|||
|
||||
# Inspired by discussions on http://bugs.python.org/issue13585
|
||||
class ContextStack(object):
|
||||
"""Context manager for programmatic management of resource cleanup
|
||||
"""Context manager for dynamic management of a stack of exit callbacks
|
||||
|
||||
For example:
|
||||
|
||||
|
|
@ -156,6 +156,13 @@ class ContextStack(object):
|
|||
"""
|
||||
def __init__(self):
|
||||
self._callbacks = deque()
|
||||
|
||||
def preserve(self):
|
||||
"""Preserve the context stack by transferring it to a new instance"""
|
||||
new_stack = type(self)()
|
||||
new_stack._callbacks = self._callbacks
|
||||
self._callbacks = deque()
|
||||
return new_stack
|
||||
|
||||
def register_exit(self, callback):
|
||||
"""Registers a callback with the standard __exit__ method signature
|
||||
|
|
|
|||
|
|
@ -207,14 +207,14 @@ API Reference
|
|||
# the with statement, even if attempts to open files later
|
||||
# in the list throw an exception
|
||||
|
||||
Each instance maintains a stack of registered callbacks (usually context
|
||||
manager exit methods) that are called in reverse order when the instance
|
||||
is closed (either explicitly or implicitly at the end of a ``with``
|
||||
statement).
|
||||
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.
|
||||
|
||||
Since registered callbacks are invoked in the reverse order of
|
||||
registration, this ends up behaving as if multiple nested ``with``
|
||||
statements had been used with the registered set of resources. 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
|
||||
an exception, then outer callbacks will be passed arguments based on that
|
||||
updated state.
|
||||
|
|
@ -245,9 +245,28 @@ API Reference
|
|||
Unlike the other methods, callbacks added this way cannot suppress
|
||||
exceptions (as they are never passed the exception details).
|
||||
|
||||
.. method:: preserve()
|
||||
|
||||
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).
|
||||
|
||||
For example, a group of files can be opened as an "all or nothing"
|
||||
operation as follows::
|
||||
|
||||
with ContextStack() as stack:
|
||||
files = [stack.enter_context(open(fname)) for fname in filenames]
|
||||
close_files = stack.preserve().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
|
||||
|
||||
.. versionadded:: 0.3
|
||||
|
||||
.. method:: close()
|
||||
|
||||
Immediately unwinds the context stack, invoking callbacks in the
|
||||
Immediately unwinds the callback stack, invoking callbacks in the
|
||||
reverse order of registration. For any context managers and exit
|
||||
callbacks registered, the arguments passed in will indicate that no
|
||||
exception occurred.
|
||||
|
|
|
|||
|
|
@ -370,14 +370,26 @@ class TestContextStack(unittest.TestCase):
|
|||
def test_close(self):
|
||||
result = []
|
||||
with ContextStack() as stack:
|
||||
@stack.register # Registered first => cleaned up last
|
||||
@stack.register
|
||||
def _exit():
|
||||
result.append(1)
|
||||
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)
|
||||
new_stack = stack.preserve()
|
||||
result.append(1)
|
||||
result.append(2)
|
||||
new_stack.close()
|
||||
self.assertEqual(result, [1, 2, 3])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
unittest.main(__name__)
|
||||
|
|
|
|||
Loading…
Reference in a new issue