]> arthur.barton.de Git - bup.git/blob - lib/bup/compat.py
Remove Client __del__ in favor of context management
[bup.git] / lib / bup / compat.py
1
2 from __future__ import absolute_import, print_function
3 from binascii import hexlify
4 from traceback import print_exception
5 import os, sys
6
7 # Please see CODINGSTYLE for important exception handling guidelines
8 # and the rationale behind add_ex_tb(), add_ex_ctx(), etc.
9
10 py_maj = sys.version_info[0]
11 py3 = py_maj >= 3
12
13 if py3:
14
15     # pylint: disable=unused-import
16     from contextlib import nullcontext
17     from os import environb as environ
18     from os import fsdecode, fsencode
19     from shlex import quote
20     # pylint: disable=undefined-variable
21     # (for python2 looking here)
22     ModuleNotFoundError = ModuleNotFoundError
23     input = input
24     range = range
25     str_type = str
26     int_types = (int,)
27
28     def hexstr(b):
29         """Return hex string (not bytes as with hexlify) representation of b."""
30         return b.hex()
31
32     def reraise(ex):
33         raise ex.with_traceback(sys.exc_info()[2])
34
35     def add_ex_tb(ex):
36         """Do nothing (already handled by Python 3 infrastructure)."""
37         return ex
38
39     def add_ex_ctx(ex, context_ex):
40         """Do nothing (already handled by Python 3 infrastructure)."""
41         return ex
42
43     class pending_raise:
44         """If rethrow is true, rethrow ex (if any), unless the body throws.
45
46         (Supports Python 2 compatibility.)
47
48         """
49         def __init__(self, ex, rethrow=True):
50             self.ex = ex
51             self.rethrow = rethrow
52         def __enter__(self):
53             return None
54         def __exit__(self, exc_type, exc_value, traceback):
55             if not exc_type and self.ex and self.rethrow:
56                 raise self.ex
57
58     def items(x):
59         return x.items()
60
61     def argv_bytes(x):
62         """Return the original bytes passed to main() for an argv argument."""
63         return fsencode(x)
64
65     def bytes_from_uint(i):
66         return bytes((i,))
67
68     def bytes_from_byte(b):  # python > 2: b[3] returns ord('x'), not b'x'
69         return bytes((b,))
70
71     byte_int = lambda x: x
72
73     def buffer(object, offset=None, size=None):
74         if size:
75             assert offset is not None
76             return memoryview(object)[offset:offset + size]
77         if offset:
78             return memoryview(object)[offset:]
79         return memoryview(object)
80
81     def getcwd():
82         return fsencode(os.getcwd())
83
84 else:  # Python 2
85
86     from contextlib import contextmanager
87
88     ModuleNotFoundError = ImportError
89
90     def fsdecode(x):
91         return x
92
93     def fsencode(x):
94         return x
95
96     from pipes import quote
97     # pylint: disable=unused-import
98     from os import environ, getcwd
99
100     # pylint: disable=unused-import
101     from bup.py2raise import reraise
102
103     @contextmanager
104     def nullcontext(enter_result=None):
105         yield enter_result
106
107     # on py3 this causes errors, obviously
108     # pylint: disable=undefined-variable
109     input = raw_input
110     # pylint: disable=undefined-variable
111     range = xrange
112     # pylint: disable=undefined-variable
113     str_type = basestring
114     # pylint: disable=undefined-variable
115     int_types = (int, long)
116
117     hexstr = hexlify
118
119     def add_ex_tb(ex):
120         """Add a traceback to ex if it doesn't already have one.  Return ex.
121
122         """
123         if not getattr(ex, '__traceback__', None):
124             ex.__traceback__ = sys.exc_info()[2]
125         return ex
126
127     def add_ex_ctx(ex, context_ex):
128         """Make context_ex the __context__ of ex (unless it already has one).
129         Return ex.
130
131         """
132         if context_ex:
133             if not getattr(ex, '__context__', None):
134                 ex.__context__ = context_ex
135         return ex
136
137     class pending_raise:
138         """If rethrow is true, rethrow ex (if any), unless the body throws.
139
140         If the body does throw, make any provided ex the __context__
141         of the newer exception (assuming there's no existing
142         __context__).  Ensure the exceptions have __tracebacks__.
143         (Supports Python 2 compatibility.)
144
145         """
146         def __init__(self, ex, rethrow=True):
147             self.ex = ex
148             self.rethrow = rethrow
149         def __enter__(self):
150             if self.ex:
151                 add_ex_tb(self.ex)
152         def __exit__(self, exc_type, exc_value, traceback):
153             if exc_value:
154                 if self.ex:
155                     add_ex_tb(exc_value)
156                     add_ex_ctx(exc_value, self.ex)
157                 return
158             if self.rethrow and self.ex:
159                 raise self.ex
160
161     def dump_traceback(ex):
162         stack = [ex]
163         next_ex = getattr(ex, '__context__', None)
164         while next_ex:
165             stack.append(next_ex)
166             next_ex = getattr(next_ex, '__context__', None)
167         stack = reversed(stack)
168         ex = next(stack)
169         tb = getattr(ex, '__traceback__', None)
170         print_exception(type(ex), ex, tb)
171         for ex in stack:
172             print('\nDuring handling of the above exception, another exception occurred:\n',
173                   file=sys.stderr)
174             tb = getattr(ex, '__traceback__', None)
175             print_exception(type(ex), ex, tb)
176
177     def items(x):
178         return x.iteritems()
179
180     def argv_bytes(x):
181         """Return the original bytes passed to main() for an argv argument."""
182         return x
183
184     bytes_from_uint = chr
185
186     def bytes_from_byte(b):
187         return b
188
189     byte_int = ord
190
191     buffer = buffer
192
193 try:
194     import bup_main
195 except ModuleNotFoundError:
196     bup_main = None
197
198 if bup_main:
199     def get_argvb():
200         "Return a new list containing the current process argv bytes."
201         return bup_main.argv()
202     if py3:
203         def get_argv():
204             "Return a new list containing the current process argv strings."
205             return [x.decode(errors='surrogateescape') for x in bup_main.argv()]
206     else:
207         def get_argv():
208             return bup_main.argv()
209 else:
210     def get_argvb():
211         raise Exception('get_argvb requires the bup_main module');
212     def get_argv():
213         raise Exception('get_argv requires the bup_main module');
214
215 def wrap_main(main):
216     """Run main() and raise a SystemExit with the return value if it
217     returns, pass along any SystemExit it raises, convert
218     KeyboardInterrupts into exit(130), and print a Python 3 style
219     contextual backtrace for other exceptions in both Python 2 and
220     3)."""
221     try:
222         sys.exit(main())
223     except KeyboardInterrupt as ex:
224         sys.exit(130)
225     except SystemExit as ex:
226         raise
227     except BaseException as ex:
228         if py3:
229             raise
230         add_ex_tb(ex)
231         dump_traceback(ex)
232         sys.exit(1)
233
234
235 # Excepting wrap_main() in the traceback, these should produce similar output:
236 #   python2 lib/bup/compat.py
237 #   python3 lib/bup/compat.py
238 # i.e.:
239 #   diff -u <(python2 lib/bup/compat.py 2>&1) <(python3 lib/bup/compat.py 2>&1)
240 #
241 # Though the python3 output for 'second' will include a stacktrace
242 # starting from wrap_main, rather than from outer().
243
244 if __name__ == '__main__':
245
246     def inner():
247         raise Exception('first')
248
249     def outer():
250         try:
251             inner()
252         except Exception as ex:
253             add_ex_tb(ex)
254             try:
255                 raise Exception('second')
256             except Exception as ex2:
257                 raise add_ex_ctx(add_ex_tb(ex2), ex)
258
259     wrap_main(outer)