]> arthur.barton.de Git - bup.git/commitdiff
compat: add "with pending_raise(ex)" to simplify nested exceptions
authorRob Browning <rlb@defaultvalue.org>
Sat, 12 Sep 2020 22:14:21 +0000 (17:14 -0500)
committerRob Browning <rlb@defaultvalue.org>
Sun, 22 Nov 2020 19:14:28 +0000 (13:14 -0600)
Thanks to Johannes Berg for the suggesting the approach.

Signed-off-by: Rob Browning <rlb@defaultvalue.org>
Tested-by: Rob Browning <rlb@defaultvalue.org>
CODINGSTYLE
Makefile
lib/bup/compat.py
lib/bup/t/tcompat.py [new file with mode: 0644]

index 69c2cfb99d5a63ddbfab2cf21e1203e61cbc294c..348a2f51340bfe3545b7f9477cde631343126e7c 100644 (file)
@@ -43,7 +43,6 @@ explicitly add stack traces to any exceptions that are going to be
 re-raised by anything other than a no-argument raise (otherwise the
 stack trace will be lost)::
 
-
   try:
       ...
   except ... as ex:
@@ -52,11 +51,24 @@ stack trace will be lost)::
   ...
   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:
       ...
@@ -69,4 +81,5 @@ of the new exception This can be accomplished via
           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.
index 4d0784bcc0d7ac15fd61330376c0ab7a2caaaa6c..5e31562ae5e771387b53b231e70219cf6a9d490d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -163,6 +163,7 @@ runtests: runtests-python runtests-cmdline
 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 \
index 39dd81063e25b84246129af00ca9165e0ed27c4f..2cd6fbaf8a3cbb01074b325ec55be62119ac7880 100644 (file)
@@ -37,6 +37,20 @@ if py3:
         """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()
 
@@ -103,6 +117,26 @@ else:  # Python 2
                 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)
diff --git a/lib/bup/t/tcompat.py b/lib/bup/t/tcompat.py
new file mode 100644 (file)
index 0000000..039eb12
--- /dev/null
@@ -0,0 +1,32 @@
+
+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))