mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-16 21:50:24 +00:00
Rename CleanupManager to ContextStack and fix usage of the test module as __main__
This commit is contained in:
parent
0077233514
commit
4e624974a6
4 changed files with 90 additions and 59 deletions
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from collections import deque
|
||||
from functools import wraps
|
||||
|
||||
__all__ = ["contextmanager", "closing", "ContextDecorator", "CleanupManager"]
|
||||
__all__ = ["contextmanager", "closing", "ContextDecorator", "ContextStack"]
|
||||
|
||||
|
||||
class ContextDecorator(object):
|
||||
|
|
@ -141,13 +141,13 @@ class closing(object):
|
|||
self.thing.close()
|
||||
|
||||
|
||||
class CleanupManager(object):
|
||||
class ContextStack(object):
|
||||
"""Context for programmatic management of resource cleanup
|
||||
|
||||
For example:
|
||||
|
||||
with CleanupManager() as cmgr:
|
||||
files = [cmgr.enter_context(fname) for fname in filenames]
|
||||
with ContextStack() as stack:
|
||||
files = [stack.enter_context(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
|
||||
|
|
@ -156,22 +156,29 @@ class CleanupManager(object):
|
|||
def __init__(self):
|
||||
self._callbacks = deque()
|
||||
|
||||
def register_exit(self, exit):
|
||||
"""Accepts callbacks with the same signature as context manager __exit__ methods
|
||||
def register_exit(self, callback):
|
||||
"""Registers a callback with the standard __exit__ method signature
|
||||
|
||||
Can also suppress exceptions the same way __exit__ methods can.
|
||||
Can suppress exceptions the same way __exit__ methods can.
|
||||
"""
|
||||
self._callbacks.append(exit)
|
||||
return exit # Allow use as a decorator
|
||||
self._callbacks.append(callback)
|
||||
return callback # Allow use as a decorator
|
||||
|
||||
def register(self, _cb, *args, **kwds):
|
||||
"""Accepts arbitrary callbacks and arguments. Cannot suppress exceptions."""
|
||||
def register(self, callback, *args, **kwds):
|
||||
"""Registers an arbitrary callback and arguments.
|
||||
|
||||
Cannot suppress exceptions.
|
||||
"""
|
||||
def _wrapper(exc_type, exc, tb):
|
||||
_cb(*args, **kwds)
|
||||
callback(*args, **kwds)
|
||||
return self.register_exit(_wrapper)
|
||||
|
||||
def enter_context(self, cm):
|
||||
"""Accepts and automatically enters other context managers"""
|
||||
"""Enters the supplied context manager
|
||||
|
||||
If successful, also registers its __exit__ method as a callback and
|
||||
returns the result of the __enter__ method.
|
||||
"""
|
||||
# We look up the special methods on the type to match the with statement
|
||||
_cm_type = type(cm)
|
||||
_exit = _cm_type.__exit__
|
||||
|
|
@ -182,7 +189,7 @@ class CleanupManager(object):
|
|||
return result
|
||||
|
||||
def close(self):
|
||||
"""Immediately cleanup all registered resources"""
|
||||
"""Immediately unwind the context stack"""
|
||||
self.__exit__(None, None, None)
|
||||
|
||||
def __enter__(self):
|
||||
|
|
@ -197,7 +204,7 @@ class CleanupManager(object):
|
|||
# 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
|
||||
# but the recursion means they're invoked in LIFO order
|
||||
cb = self._callbacks.popleft()
|
||||
if not self._callbacks:
|
||||
# Innermost callback is invoked directly
|
||||
|
|
|
|||
|
|
@ -188,43 +188,66 @@ API Reference
|
|||
This may involve keeping a copy of the original arguments used to
|
||||
first initialise the context manager.
|
||||
|
||||
.. class:: CleanupManager()
|
||||
|
||||
.. class:: ContextStack()
|
||||
|
||||
A context manager that is designed to make it easy to programmatically
|
||||
combine other context managers and cleanup functions, that are either
|
||||
optional or driven by input data.
|
||||
combine other context managers and cleanup functions, especially those
|
||||
that are optional or otherwise driven by input data.
|
||||
|
||||
For example, a set of files may easily be handled in a single with
|
||||
statement as follows::
|
||||
|
||||
with CleanupManager() as cmgr:
|
||||
files = [cmgr.enter_context(fname) for fname in filenames]
|
||||
with ContextStack() as stack:
|
||||
files = [stack.enter_context(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
|
||||
|
||||
.. method:: register_exit(exit):
|
||||
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).
|
||||
|
||||
Accepts callbacks with the same signature as context manager
|
||||
:meth:`__exit__` methods
|
||||
|
||||
By returing true values, these callbacks can suppress exceptions the
|
||||
same way context manager :meth:`__exit__` methods can.
|
||||
|
||||
.. method:: register(_cb, *args, **kwds):
|
||||
|
||||
Accepts arbitrary callbacks and arguments. These callbacks cannot
|
||||
suppress exceptions.
|
||||
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
|
||||
extends to exception handling - if an inner callback suppresses or replaces
|
||||
an exception, then outer callbacks will be passed arguments based on that
|
||||
that updated state.
|
||||
|
||||
.. method:: enter_context(cm):
|
||||
|
||||
Accepts and automatically enters other context managers. These
|
||||
context managers may suppress exceptions just as they normally would.
|
||||
Enters a new context manager and adds its :meth:`__exit__` method to
|
||||
the callback stack. The return value is the result of the context
|
||||
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.
|
||||
|
||||
.. method:: register_exit(callback):
|
||||
|
||||
Directly accepts a callback with the same signature as a
|
||||
context manager's :meth:`__exit__` method and adds it to the callback
|
||||
stack.
|
||||
|
||||
By returning true values, these callbacks can suppress exceptions the
|
||||
same way context manager :meth:`__exit__` methods can.
|
||||
|
||||
.. method:: push_callback(callback, *args, **kwds):
|
||||
|
||||
Accepts an arbitrary callback function and arguments and adds it to
|
||||
the callback stack.
|
||||
|
||||
Unlike the other methods, callbacks added this way cannot suppress
|
||||
exceptions (as they are never passed the exception details).
|
||||
|
||||
.. method:: close()
|
||||
|
||||
Immediately cleans up all registered resources, resetting the manager
|
||||
to its initial state in the process.
|
||||
Immediately unwinds the context 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.
|
||||
|
||||
|
||||
Obtaining the Module
|
||||
|
|
@ -241,7 +264,7 @@ PyPI page`_.
|
|||
There are no operating system or distribution specific versions of this
|
||||
module - it is a pure Python module that should work on all platforms.
|
||||
|
||||
Supported Python versions are 2.7 and 3.2+.
|
||||
Supported Python versions are currently 2.7 and 3.2+.
|
||||
|
||||
.. _Python Package Index: http://pypi.python.org
|
||||
.. _pip: http://www.pip-installer.org
|
||||
|
|
@ -251,7 +274,7 @@ Supported Python versions are 2.7 and 3.2+.
|
|||
Development and Support
|
||||
-----------------------
|
||||
|
||||
WalkDir is developed and maintained on BitBucket_. Problems and suggested
|
||||
contextlib2 is developed and maintained on BitBucket_. Problems and suggested
|
||||
improvements can be posted to the `issue tracker`_.
|
||||
|
||||
.. _BitBucket: https://bitbucket.org/ncoghlan/contextlib2/overview
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -2,7 +2,7 @@ from distutils.core import setup
|
|||
|
||||
setup(
|
||||
name='contextlib2',
|
||||
version='0.1',
|
||||
version='0.2',
|
||||
py_modules=['contextlib2'],
|
||||
license='PSF License',
|
||||
description='Backports and enhancements for the contextlib module',
|
||||
|
|
|
|||
43
test_contextlib2.py
Normal file → Executable file
43
test_contextlib2.py
Normal file → Executable file
|
|
@ -163,7 +163,7 @@ class TestContextDecorator(unittest.TestCase):
|
|||
raise NameError('foo')
|
||||
self.assertIsNotNone(context.exc)
|
||||
self.assertIs(context.exc[0], NameError)
|
||||
self.assertIn('foo', context.exc[1])
|
||||
self.assertIn('foo', str(context.exc[1]))
|
||||
|
||||
context = mycontext()
|
||||
context.catch = True
|
||||
|
|
@ -197,7 +197,7 @@ class TestContextDecorator(unittest.TestCase):
|
|||
test()
|
||||
self.assertIsNotNone(context.exc)
|
||||
self.assertIs(context.exc[0], NameError)
|
||||
self.assertIn('foo', context.exc[1])
|
||||
self.assertIn('foo', str(context.exc[1]))
|
||||
|
||||
|
||||
def test_decorating_method(self):
|
||||
|
|
@ -299,10 +299,10 @@ class TestContextDecorator(unittest.TestCase):
|
|||
self.assertEqual(state, [1, 'something else', 999])
|
||||
|
||||
|
||||
class TestCleanupManager(unittest.TestCase):
|
||||
class TestContextStack(unittest.TestCase):
|
||||
|
||||
def test_no_resources(self):
|
||||
with CleanupManager():
|
||||
with ContextStack():
|
||||
pass
|
||||
|
||||
def test_register(self):
|
||||
|
|
@ -317,16 +317,16 @@ class TestCleanupManager(unittest.TestCase):
|
|||
result = []
|
||||
def _exit(*args, **kwds):
|
||||
result.append((args, kwds))
|
||||
with CleanupManager() as cmgr:
|
||||
with ContextStack() as stack:
|
||||
for args, kwds in reversed(expected):
|
||||
if args and kwds:
|
||||
cmgr.register(_exit, *args, **kwds)
|
||||
stack.register(_exit, *args, **kwds)
|
||||
elif args:
|
||||
cmgr.register(_exit, *args)
|
||||
stack.register(_exit, *args)
|
||||
elif kwds:
|
||||
cmgr.register(_exit, **kwds)
|
||||
stack.register(_exit, **kwds)
|
||||
else:
|
||||
cmgr.register(_exit)
|
||||
stack.register(_exit)
|
||||
|
||||
def test_register_exit(self):
|
||||
exc_raised = ZeroDivisionError
|
||||
|
|
@ -338,11 +338,11 @@ class TestCleanupManager(unittest.TestCase):
|
|||
self.assertIsNone(exc_type)
|
||||
self.assertIsNone(exc)
|
||||
self.assertIsNone(exc_tb)
|
||||
with CleanupManager() as cmgr:
|
||||
cmgr.register_exit(_expect_ok)
|
||||
cmgr.register_exit(_suppress_exc)
|
||||
cmgr.register_exit(_expect_exc)
|
||||
cmgr.register_exit(_expect_exc)
|
||||
with ContextStack() as stack:
|
||||
stack.register_exit(_expect_ok)
|
||||
stack.register_exit(_suppress_exc)
|
||||
stack.register_exit(_expect_exc)
|
||||
stack.register_exit(_expect_exc)
|
||||
1/0
|
||||
|
||||
def test_enter_context(self):
|
||||
|
|
@ -353,24 +353,25 @@ class TestCleanupManager(unittest.TestCase):
|
|||
result.append(3)
|
||||
|
||||
result = []
|
||||
with CleanupManager() as cmgr:
|
||||
@cmgr.register # Registered first => cleaned up last
|
||||
with ContextStack() as stack:
|
||||
@stack.register # Registered first => cleaned up last
|
||||
def _exit():
|
||||
result.append(4)
|
||||
cmgr.enter_context(TestCM())
|
||||
stack.enter_context(TestCM())
|
||||
result.append(2)
|
||||
self.assertEqual(result, [1, 2, 3, 4])
|
||||
|
||||
def test_close(self):
|
||||
result = []
|
||||
with CleanupManager() as cmgr:
|
||||
@cmgr.register # Registered first => cleaned up last
|
||||
with ContextStack() as stack:
|
||||
@stack.register # Registered first => cleaned up last
|
||||
def _exit():
|
||||
result.append(1)
|
||||
cmgr.close()
|
||||
stack.close()
|
||||
result.append(2)
|
||||
self.assertEqual(result, [1, 2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_main()
|
||||
import unittest
|
||||
unittest.main(__name__)
|
||||
|
|
|
|||
Loading…
Reference in a new issue