comparison env/lib/python3.9/site-packages/boltons/debugutils.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
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()