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