Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/boltons/debugutils.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
1 # -*- coding: utf-8 -*- | |
2 """ | |
3 A small set of utilities useful for debugging misbehaving | |
4 applications. Currently this focuses on ways to use :mod:`pdb`, the | |
5 built-in Python debugger. | |
6 """ | |
7 | |
8 import sys | |
9 import time | |
10 | |
11 try: | |
12 basestring | |
13 from repr import Repr | |
14 except NameError: | |
15 basestring = (str, bytes) # py3 | |
16 from reprlib import Repr | |
17 | |
18 try: | |
19 from typeutils import make_sentinel | |
20 _UNSET = make_sentinel(var_name='_UNSET') | |
21 except ImportError: | |
22 _UNSET = object() | |
23 | |
24 __all__ = ['pdb_on_signal', 'pdb_on_exception', 'wrap_trace'] | |
25 | |
26 | |
27 def pdb_on_signal(signalnum=None): | |
28 """Installs a signal handler for *signalnum*, which defaults to | |
29 ``SIGINT``, or keyboard interrupt/ctrl-c. This signal handler | |
30 launches a :mod:`pdb` breakpoint. Results vary in concurrent | |
31 systems, but this technique can be useful for debugging infinite | |
32 loops, or easily getting into deep call stacks. | |
33 | |
34 Args: | |
35 signalnum (int): The signal number of the signal to handle | |
36 with pdb. Defaults to :mod:`signal.SIGINT`, see | |
37 :mod:`signal` for more information. | |
38 """ | |
39 import pdb | |
40 import signal | |
41 if not signalnum: | |
42 signalnum = signal.SIGINT | |
43 | |
44 old_handler = signal.getsignal(signalnum) | |
45 | |
46 def pdb_int_handler(sig, frame): | |
47 signal.signal(signalnum, old_handler) | |
48 pdb.set_trace() | |
49 pdb_on_signal(signalnum) # use 'u' to find your code and 'h' for help | |
50 | |
51 signal.signal(signalnum, pdb_int_handler) | |
52 return | |
53 | |
54 | |
55 def pdb_on_exception(limit=100): | |
56 """Installs a handler which, instead of exiting, attaches a | |
57 post-mortem pdb console whenever an unhandled exception is | |
58 encountered. | |
59 | |
60 Args: | |
61 limit (int): the max number of stack frames to display when | |
62 printing the traceback | |
63 | |
64 A similar effect can be achieved from the command-line using the | |
65 following command:: | |
66 | |
67 python -m pdb your_code.py | |
68 | |
69 But ``pdb_on_exception`` allows you to do this conditionally and within | |
70 your application. To restore default behavior, just do:: | |
71 | |
72 sys.excepthook = sys.__excepthook__ | |
73 """ | |
74 import pdb | |
75 import sys | |
76 import traceback | |
77 | |
78 def pdb_excepthook(exc_type, exc_val, exc_tb): | |
79 traceback.print_tb(exc_tb, limit=limit) | |
80 pdb.post_mortem(exc_tb) | |
81 | |
82 sys.excepthook = pdb_excepthook | |
83 return | |
84 | |
85 _repr_obj = Repr() | |
86 _repr_obj.maxstring = 50 | |
87 _repr_obj.maxother = 50 | |
88 brief_repr = _repr_obj.repr | |
89 | |
90 | |
91 # events: call, return, get, set, del, raise | |
92 def trace_print_hook(event, label, obj, attr_name, | |
93 args=(), kwargs={}, result=_UNSET): | |
94 fargs = (event.ljust(6), time.time(), label.rjust(10), | |
95 obj.__class__.__name__, attr_name) | |
96 if event == 'get': | |
97 tmpl = '%s %s - %s - %s.%s -> %s' | |
98 fargs += (brief_repr(result),) | |
99 elif event == 'set': | |
100 tmpl = '%s %s - %s - %s.%s = %s' | |
101 fargs += (brief_repr(args[0]),) | |
102 elif event == 'del': | |
103 tmpl = '%s %s - %s - %s.%s' | |
104 else: # call/return/raise | |
105 tmpl = '%s %s - %s - %s.%s(%s)' | |
106 fargs += (', '.join([brief_repr(a) for a in args]),) | |
107 if kwargs: | |
108 tmpl = '%s %s - %s - %s.%s(%s, %s)' | |
109 fargs += (', '.join(['%s=%s' % (k, brief_repr(v)) | |
110 for k, v in kwargs.items()]),) | |
111 if result is not _UNSET: | |
112 tmpl += ' -> %s' | |
113 fargs += (brief_repr(result),) | |
114 print(tmpl % fargs) | |
115 return | |
116 | |
117 | |
118 def wrap_trace(obj, hook=trace_print_hook, | |
119 which=None, events=None, label=None): | |
120 """Monitor an object for interactions. Whenever code calls a method, | |
121 gets an attribute, or sets an attribute, an event is called. By | |
122 default the trace output is printed, but a custom tracing *hook* | |
123 can be passed. | |
124 | |
125 Args: | |
126 obj (object): New- or old-style object to be traced. Built-in | |
127 objects like lists and dicts also supported. | |
128 hook (callable): A function called once for every event. See | |
129 below for details. | |
130 which (str): One or more attribute names to trace, or a | |
131 function accepting attribute name and value, and returning | |
132 True/False. | |
133 events (str): One or more kinds of events to call *hook* | |
134 on. Expected values are ``['get', 'set', 'del', 'call', | |
135 'raise', 'return']``. Defaults to all events. | |
136 label (str): A name to associate with the traced object | |
137 Defaults to hexadecimal memory address, similar to repr. | |
138 | |
139 The object returned is not the same object as the one passed | |
140 in. It will not pass identity checks. However, it will pass | |
141 :func:`isinstance` checks, as it is a new instance of a new | |
142 subtype of the object passed. | |
143 | |
144 """ | |
145 # other actions: pdb.set_trace, print, aggregate, aggregate_return | |
146 # (like aggregate but with the return value) | |
147 | |
148 # TODO: test classmethod/staticmethod/property | |
149 # TODO: wrap __dict__ for old-style classes? | |
150 | |
151 if isinstance(which, basestring): | |
152 which_func = lambda attr_name, attr_val: attr_name == which | |
153 elif callable(getattr(which, '__contains__', None)): | |
154 which_func = lambda attr_name, attr_val: attr_name in which | |
155 elif which is None or callable(which): | |
156 which_func = which | |
157 else: | |
158 raise TypeError('expected attr name(s) or callable, not: %r' % which) | |
159 | |
160 label = label or hex(id(obj)) | |
161 | |
162 if isinstance(events, basestring): | |
163 events = [events] | |
164 do_get = not events or 'get' in events | |
165 do_set = not events or 'set' in events | |
166 do_del = not events or 'del' in events | |
167 do_call = not events or 'call' in events | |
168 do_raise = not events or 'raise' in events | |
169 do_return = not events or 'return' in events | |
170 | |
171 def wrap_method(attr_name, func, _hook=hook, _label=label): | |
172 def wrapped(*a, **kw): | |
173 a = a[1:] | |
174 if do_call: | |
175 hook(event='call', label=_label, obj=obj, | |
176 attr_name=attr_name, args=a, kwargs=kw) | |
177 if do_raise: | |
178 try: | |
179 ret = func(*a, **kw) | |
180 except: | |
181 if not hook(event='raise', label=_label, obj=obj, | |
182 attr_name=attr_name, args=a, kwargs=kw, | |
183 result=sys.exc_info()): | |
184 raise | |
185 else: | |
186 ret = func(*a, **kw) | |
187 if do_return: | |
188 hook(event='return', label=_label, obj=obj, | |
189 attr_name=attr_name, args=a, kwargs=kw, result=ret) | |
190 return ret | |
191 | |
192 wrapped.__name__ = func.__name__ | |
193 wrapped.__doc__ = func.__doc__ | |
194 try: | |
195 wrapped.__module__ = func.__module__ | |
196 except Exception: | |
197 pass | |
198 try: | |
199 if func.__dict__: | |
200 wrapped.__dict__.update(func.__dict__) | |
201 except Exception: | |
202 pass | |
203 return wrapped | |
204 | |
205 def __getattribute__(self, attr_name): | |
206 ret = type(obj).__getattribute__(obj, attr_name) | |
207 if callable(ret): # wrap any bound methods | |
208 ret = type(obj).__getattribute__(self, attr_name) | |
209 if do_get: | |
210 hook('get', label, obj, attr_name, (), {}, result=ret) | |
211 return ret | |
212 | |
213 def __setattr__(self, attr_name, value): | |
214 type(obj).__setattr__(obj, attr_name, value) | |
215 if do_set: | |
216 hook('set', label, obj, attr_name, (value,), {}) | |
217 return | |
218 | |
219 def __delattr__(self, attr_name): | |
220 type(obj).__delattr__(obj, attr_name) | |
221 if do_del: | |
222 hook('del', label, obj, attr_name, (), {}) | |
223 return | |
224 | |
225 attrs = {} | |
226 for attr_name in dir(obj): | |
227 try: | |
228 attr_val = getattr(obj, attr_name) | |
229 except Exception: | |
230 continue | |
231 | |
232 if not callable(attr_val) or attr_name in ('__new__',): | |
233 continue | |
234 elif which_func and not which_func(attr_name, attr_val): | |
235 continue | |
236 | |
237 if attr_name == '__getattribute__': | |
238 wrapped_method = __getattribute__ | |
239 elif attr_name == '__setattr__': | |
240 wrapped_method = __setattr__ | |
241 elif attr_name == '__delattr__': | |
242 wrapped_method = __delattr__ | |
243 else: | |
244 wrapped_method = wrap_method(attr_name, attr_val) | |
245 attrs[attr_name] = wrapped_method | |
246 | |
247 cls_name = obj.__class__.__name__ | |
248 if cls_name == cls_name.lower(): | |
249 type_name = 'traced_' + cls_name | |
250 else: | |
251 type_name = 'Traced' + cls_name | |
252 | |
253 if hasattr(obj, '__mro__'): | |
254 bases = (obj.__class__,) | |
255 else: | |
256 # need new-style class for even basic wrapping of callables to | |
257 # work. getattribute won't work for old-style classes of course. | |
258 bases = (obj.__class__, object) | |
259 | |
260 trace_type = type(type_name, bases, attrs) | |
261 for cls in trace_type.__mro__: | |
262 try: | |
263 return cls.__new__(trace_type) | |
264 except Exception: | |
265 pass | |
266 raise TypeError('unable to wrap_trace %r instance %r' | |
267 % (obj.__class__, obj)) | |
268 | |
269 | |
270 if __name__ == '__main__': | |
271 obj = wrap_trace({}) | |
272 obj['hi'] = 'hello' | |
273 obj.fail | |
274 import pdb;pdb.set_trace() |