Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/boltons/debugutils.py @ 0:d30785e31577 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:18:57 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:d30785e31577 |
|---|---|
| 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() |
