mirror of
https://github.com/jazzband/contextlib2.git
synced 2026-03-16 21:50:24 +00:00
Issue #5: support old-style classes in ExitStack
This commit is contained in:
parent
c8efb9adcc
commit
911b459522
4 changed files with 52 additions and 7 deletions
4
NEWS.rst
4
NEWS.rst
|
|
@ -4,7 +4,9 @@ Release History
|
|||
0.5.3 (not yet released)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
* TBD
|
||||
* ``ExitStack`` now correctly handles context managers implemented as old-style
|
||||
classes in Python 2.x (such as ``codecs.StreamReader`` and
|
||||
``codecs.StreamWriter``)
|
||||
|
||||
0.5.2 (2016-05-02)
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -288,6 +288,20 @@ else:
|
|||
exc_type, exc_value, exc_tb = exc_details
|
||||
exec ("raise exc_type, exc_value, exc_tb")
|
||||
|
||||
# Handle old-style classes if they exist
|
||||
try:
|
||||
from types import InstanceType
|
||||
except ImportError:
|
||||
# Python 3 doesn't have old-style classes
|
||||
_get_type = type
|
||||
else:
|
||||
# Need to handle old-style context managers on Python 2
|
||||
def _get_type(obj):
|
||||
obj_type = type(obj)
|
||||
if obj_type is InstanceType:
|
||||
return obj.__class__ # Old-style class
|
||||
return obj_type # New-style class
|
||||
|
||||
# Inspired by discussions on http://bugs.python.org/issue13585
|
||||
class ExitStack(object):
|
||||
"""Context manager for dynamic management of a stack of exit callbacks
|
||||
|
|
@ -328,7 +342,7 @@ class ExitStack(object):
|
|||
"""
|
||||
# We use an unbound method rather than a bound method to follow
|
||||
# the standard lookup behaviour for special methods
|
||||
_cb_type = type(exit)
|
||||
_cb_type = _get_type(exit)
|
||||
try:
|
||||
exit_method = _cb_type.__exit__
|
||||
except AttributeError:
|
||||
|
|
@ -358,7 +372,7 @@ class ExitStack(object):
|
|||
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)
|
||||
_cm_type = _get_type(cm)
|
||||
_exit = _cm_type.__exit__
|
||||
result = _cm_type.__enter__(cm)
|
||||
self._push_cm_exit(cm, _exit)
|
||||
|
|
|
|||
|
|
@ -310,10 +310,6 @@ Functions and classes provided:
|
|||
foundation for higher level context managers that manipulate the exit
|
||||
stack in application specific ways.
|
||||
|
||||
Context managers used with :class:`ExitStack` must be new-style classes -
|
||||
this is the default on Python 3, but requires explicitly inheriting from
|
||||
:class:`object` or another new-style class in Python 2.
|
||||
|
||||
.. versionadded:: 0.4
|
||||
Part of the standard library in Python 3.3 and later
|
||||
|
||||
|
|
|
|||
|
|
@ -706,6 +706,39 @@ class TestExitStack(unittest.TestCase):
|
|||
stack.push(cm)
|
||||
self.assertIs(stack._exit_callbacks[-1], cm)
|
||||
|
||||
def test_default_class_semantics(self):
|
||||
# For Python 2.x, this ensures compatibility with old-style classes
|
||||
# For Python 3.x, it just reruns some of the other tests
|
||||
class DefaultCM:
|
||||
def __enter__(self):
|
||||
result.append("Enter")
|
||||
def __exit__(self, *exc_details):
|
||||
result.append("Exit")
|
||||
class DefaultCallable:
|
||||
def __call__(self, *exc_details):
|
||||
result.append("Callback")
|
||||
|
||||
result = []
|
||||
cm = DefaultCM()
|
||||
cb = DefaultCallable()
|
||||
with ExitStack() as stack:
|
||||
stack.enter_context(cm)
|
||||
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
|
||||
stack.push(cb)
|
||||
stack.push(cm)
|
||||
self.assertIs(stack._exit_callbacks[-1].__self__, cm)
|
||||
result.append("Running")
|
||||
stack.callback(cb)
|
||||
self.assertIs(stack._exit_callbacks[-1].__wrapped__, cb)
|
||||
self.assertEqual(result, ["Enter", "Running",
|
||||
"Callback", "Exit",
|
||||
"Callback", "Exit",
|
||||
])
|
||||
|
||||
with ExitStack():
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class TestRedirectStream:
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue