]> arthur.barton.de Git - bup.git/blob - lib/bup/compat.py
Support exception chaining and tracebacks
[bup.git] / lib / bup / compat.py
1
2 from __future__ import print_function
3 from traceback import print_exception
4 import sys
5
6 py_maj = sys.version_info[0]
7 py3 = py_maj >= 3
8
9 if py3:
10
11     def add_ex_tb(ex):
12         pass
13
14     def chain_ex(ex, context_ex):
15         return ex
16
17 else:  # Python 2
18
19     def add_ex_tb(ex):
20         if not getattr(ex, '__traceback__', None):
21             ex.__traceback__ = sys.exc_info()[2]
22
23     def chain_ex(ex, context_ex):
24         if context_ex:
25             add_ex_tb(context_ex)
26             if not getattr(ex, '__context__', None):
27                 ex.__context__ = context_ex
28         return ex
29
30     def dump_traceback(ex):
31         stack = [ex]
32         next_ex = getattr(ex, '__context__', None)
33         while next_ex:
34             stack.append(next_ex)
35             next_ex = getattr(next_ex, '__context__', None)
36         stack = reversed(stack)
37         ex = next(stack)
38         tb = getattr(ex, '__traceback__', None)
39         print_exception(type(ex), ex, tb)
40         for ex in stack:
41             print('\nDuring handling of the above exception, another exception occurred:\n',
42                   file=sys.stderr)
43             tb = getattr(ex, '__traceback__', None)
44             print_exception(type(ex), ex, tb)
45
46 def wrap_main(main):
47     """Run main() and raise a SystemExit with the return value if it
48     returns, pass along any SystemExit it raises, convert
49     KeyboardInterrupts into exit(130), and print a Python 3 style
50     contextual backtrace for other exceptions in both Python 2 and
51     3)."""
52     try:
53         sys.exit(main())
54     except KeyboardInterrupt as ex:
55         sys.exit(130)
56     except SystemExit as ex:
57         raise
58     except BaseException as ex:
59         if py3:
60             raise
61         add_ex_tb(ex)
62         dump_traceback(ex)
63         sys.exit(1)
64
65
66 # Excepting wrap_main() in the traceback, these should produce the same output:
67 #   python2 lib/bup/compat.py
68 #   python3 lib/bup/compat.py
69 # i.e.:
70 #   diff -u <(python2 lib/bup/compat.py 2>&1) <(python3 lib/bup/compat.py 2>&1)
71
72 if __name__ == '__main__':
73
74     def inner():
75         raise Exception('first')
76
77     def outer():
78         try:
79             inner()
80         except Exception as ex:
81             raise chain_ex(Exception('second'), ex)
82
83     wrap_main(outer)