Thanks to Johannes Berg for the suggesting the approach.
Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Rob Browning <rlb@defaultvalue.org>
re-raised by anything other than a no-argument raise (otherwise the
stack trace will be lost)::
-
try:
...
except ... as ex:
...
raise pending_ex
-If an exception is thrown from an exception handler, the pending
+When an exception is thrown from an exception handler, the pending
exception should be the `"context"
<https://docs.python.org/3/reference/simple_stmts.html#the-raise-statement>`_
-of the new exception This can be accomplished via
-``add_ex_ctx()``::
+of the new exception, which can be accomplished (portably) via
+``pending_raise()``::
+
+ try:
+ ...
+ except ... as ex:
+ with pending_raise(ex):
+ clean_up()
+
+This should do roughly the same thing in Python 2 and Python 3,
+throwing any exception from ``clean_up()`` after adding ex as the
+``__context__`` if clean_up() throws, and throwing ``ex`` otherwise.
+
+If for some reason, you need more control, you can use
+``add_ex_ctx()`` directly::
try:
...
raise add_ex_ctx(ex2, ex)
raise
-See the end of ``lib/bup/compat.py`` for a functional example.
+See the end of ``lib/bup/compat.py`` for a functional example, and all
+of this can be removed once we drop support for Python 2.
python_tests := \
lib/bup/t/tbloom.py \
lib/bup/t/tclient.py \
+ lib/bup/t/tcompat.py \
lib/bup/t/tgit.py \
lib/bup/t/thashsplit.py \
lib/bup/t/thelpers.py \
"""Do nothing (already handled by Python 3 infrastructure)."""
return ex
+ class pending_raise:
+ """Rethrow either the provided ex, or any exception raised by the with
+ statement body. (Supports Python 2 compatibility.)
+
+ """
+ def __init__(self, ex):
+ self.ex = ex
+ def __enter__(self):
+ return None
+ def __exit__(self, exc_type, exc_value, traceback):
+ if not exc_type:
+ raise self.ex
+ return None
+
def items(x):
return x.items()
ex.__context__ = context_ex
return ex
+ class pending_raise:
+ """Rethrow either the provided ex, or any exception raised by the with
+ statement body, after making ex the __context__ of the newer
+ exception (assuming there's no existing __context__). Ensure
+ the exceptions have __tracebacks__. (Supports Python 2
+ compatibility.)
+
+ """
+ def __init__(self, ex):
+ self.ex = ex
+ def __enter__(self):
+ add_ex_tb(self.ex)
+ return None
+ def __exit__(self, exc_type, exc_value, traceback):
+ if not exc_type:
+ raise self.ex
+ add_ex_tb(exc_value)
+ add_ex_ctx(exc_value, self.ex)
+ return None
+
def dump_traceback(ex):
stack = [ex]
next_ex = getattr(ex, '__context__', None)
--- /dev/null
+
+from __future__ import absolute_import, print_function
+
+from wvtest import *
+
+from bup.compat import pending_raise
+
+@wvtest
+def test_pending_raise():
+ outer = Exception('outer')
+ inner = Exception('inner')
+
+ try:
+ try:
+ raise outer
+ except Exception as ex:
+ with pending_raise(ex):
+ pass
+ except Exception as ex:
+ WVPASSEQ(outer, ex)
+ WVPASSEQ(None, getattr(outer, '__context__', None))
+
+ try:
+ try:
+ raise outer
+ except Exception as ex:
+ with pending_raise(ex):
+ raise inner
+ except Exception as ex:
+ WVPASSEQ(inner, ex)
+ WVPASSEQ(None, getattr(outer, '__context__', None))
+ WVPASSEQ(outer, getattr(inner, '__context__', None))