]> arthur.barton.de Git - bup.git/blob - lib/bup/compat.py
chain_ex: don't opportunistically add tracebacks to context_ex
[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     str_type = str
12
13     def add_ex_tb(ex):
14         """Do nothing (already handled by Python 3 infrastructure)."""
15         return ex
16
17     def chain_ex(ex, context_ex):
18         """Do nothing (already handled by Python 3 infrastructure)."""
19         return ex
20
21 else:  # Python 2
22
23     str_type = basestring
24
25     def add_ex_tb(ex):
26         """Add a traceback to ex if it doesn't already have one.  Return ex.
27
28         """
29         if not getattr(ex, '__traceback__', None):
30             ex.__traceback__ = sys.exc_info()[2]
31         return ex
32
33     def chain_ex(ex, context_ex):
34         """Chain context_ex to ex as the __context__ (unless it already has
35         one).  Return ex.
36
37         """
38         if context_ex:
39             if not getattr(ex, '__context__', None):
40                 ex.__context__ = context_ex
41         return ex
42
43     def dump_traceback(ex):
44         stack = [ex]
45         next_ex = getattr(ex, '__context__', None)
46         while next_ex:
47             stack.append(next_ex)
48             next_ex = getattr(next_ex, '__context__', None)
49         stack = reversed(stack)
50         ex = next(stack)
51         tb = getattr(ex, '__traceback__', None)
52         print_exception(type(ex), ex, tb)
53         for ex in stack:
54             print('\nDuring handling of the above exception, another exception occurred:\n',
55                   file=sys.stderr)
56             tb = getattr(ex, '__traceback__', None)
57             print_exception(type(ex), ex, tb)
58
59 def wrap_main(main):
60     """Run main() and raise a SystemExit with the return value if it
61     returns, pass along any SystemExit it raises, convert
62     KeyboardInterrupts into exit(130), and print a Python 3 style
63     contextual backtrace for other exceptions in both Python 2 and
64     3)."""
65     try:
66         sys.exit(main())
67     except KeyboardInterrupt as ex:
68         sys.exit(130)
69     except SystemExit as ex:
70         raise
71     except BaseException as ex:
72         if py3:
73             raise
74         add_ex_tb(ex)
75         dump_traceback(ex)
76         sys.exit(1)
77
78
79 # Excepting wrap_main() in the traceback, these should produce similar output:
80 #   python2 lib/bup/compat.py
81 #   python3 lib/bup/compat.py
82 # i.e.:
83 #   diff -u <(python2 lib/bup/compat.py 2>&1) <(python3 lib/bup/compat.py 2>&1)
84 #
85 # Though the python3 output for 'second' will include a stacktrace
86 # starting from wrap_main, rather than from outer().
87
88 if __name__ == '__main__':
89
90     def inner():
91         raise Exception('first')
92
93     def outer():
94         try:
95             inner()
96         except Exception as ex:
97             add_ex_tb(ex)
98             try:
99                 raise Exception('second')
100             except Exception as ex2:
101                 raise chain_ex(add_ex_tb(ex2), ex)
102
103     wrap_main(outer)