Source code for arsenal.debug.util

"""
Debugging utilities.
"""

import re
import sys
import inspect
import traceback


#from arsenal.debug import saverr  # registers hook
try:
    from IPython import embed as ip
except ImportError:
    try:
        from IPython.frontend.terminal.embed import InteractiveShellEmbed
    except ImportError:
        from IPython.terminal.embed import InteractiveShellEmbed

    def ip(banner1='', **kw):
        shell = InteractiveShellEmbed.instance(banner1=banner1, **kw)
        shell(header='', stack_depth=2)


[docs]def enable_ultratb(mode='Context', **kwargs): from IPython.core import ultratb sys.excepthook = ultratb.FormattedTB(mode=mode, **kwargs)
# TODO: look IPython's debugging stuff.. # http://ipython.org/ipython-doc/dev/api/generated/IPython.core.debugger.html from IPython.core.debugger import Tracer set_trace = lambda: Tracer()() #from IPython.Debugger import Pdb from pdb import set_trace, pm, Pdb
[docs]def enable_debug_hook(): "Register pdb's post-mortem debugger as the handler for uncaught exceptions." def debug_hook(*args): sys.__excepthook__(*args) pm() sys.excepthook = debug_hook
enable_pm = enable_debug_hook
[docs]def dumpobj(o, callables=False, private=False): """ >>> class A(object): ... x = 10 ... def __init__(self, y): ... self.y = y ... def span(self): ... pass ... def __repr__(self): ... return 'A(%r, %r)' % (self.x, self.y) ... >>> dumpobj(A('hello')) A(10, 'hello') instance of: A x: int y: str >>> dumpobj(A('hello'), callables=0, private=True) A(10, 'hello') instance of: A __dict__: dict __doc__: NoneType __module__: str __weakref__: NoneType x: int y: str """ print(repr(o)) print('instance of:', type(o).__name__) for a in dir(o): if not callables and callable(getattr(o, a)): continue if not private and a.startswith('__'): continue try: print('%20s: %s' % (a, type(getattr(o,a)).__name__)) except: pass
[docs]def debug(s, *args, **kwargs): """ >>> def foo(): ... bar = 'world' ... debug('hello {bar}') >>> foo() hello world """ c_frame = inspect.getouterframes(inspect.currentframe(), 1)[1][0] c_args, c_varargs, c_varkw, c_locals = inspect.getargvalues(c_frame) d = dict(c_locals) d.update(kwargs) print(s.format(*args, **d))
debug_expr_fmt = '[DEBUG:%s] %s -> %r' # borrowed from IPython
[docs]def debug_expr(expr): """ Evaluate and print the value of a string representing a python expression in the caller's frame. Takes an expression, evaluates it in the caller's frame and prints both the given expression and the resulting value (as well as a debug mark indicating the name of the calling function. The input must be of a form suitable for eval(). >>> def foo(): ... x = 15 ... debug_expr('x') ... debug_expr('x**2 + 2*x + 3') ... f = lambda x: x**2 ... debug_expr('f(x)') >>> foo() [DEBUG:foo] x -> 15 [DEBUG:foo] x**2 + 2*x + 3 -> 258 [DEBUG:foo] f(x) -> 225 """ cf = sys._getframe(1) # caller frame val = eval(expr, cf.f_globals, cf.f_locals) print(debug_expr_fmt % (cf.f_code.co_name, expr, val))
[docs]def debugx(obj): """ I often write debugging print statements which look like >>> somevar = 'somevalue' >>> print('somevar:', somevar) somevar: somevalue What this function attempts to do is provide a shortcut >>> debugx(somevar) somevar: somevalue Note: that we do not need to pass strings to this function. >>> def foo(): ... x = 15 ... debugx(x) ... debugx(x**2 + 2*x + 3) ... f = lambda x: x**2 ... debugx(f(x)) >>> foo() x: 15 x**2 + 2*x + 3: 258 f(x): 225 Warning: this should only be used for debugging because it relies on introspection, which can be really slow and sometimes even buggy. """ cf = sys._getframe(1) ctx_lines = inspect.getframeinfo(cf).code_context code = ''.join(map(str.strip, ctx_lines)) code = re.sub('debugx\((.*)\)', r'\1', code) print(code + ':', obj)
# TIMV: would it be possibe to change this function to work without raising # and exception?
[docs]def framedump(): """ Print the usual traceback information, followed by a listing of all the local variables in each frame. If this function is called when an exception has been thrown the framedump will start at the origin of the exception not where it was caught. """ # Move to the frame where the exception occurred, which is often not the # same frame where the exception was caught. tb = sys.exc_info()[2] if tb is not None: while 1: if not tb.tb_next: break tb = tb.tb_next f = tb.tb_frame else: # no exception occurred f = sys._getframe() # get the stack frames stack = [] while f: stack.append(f) f = f.f_back stack.reverse() print('Traceback:') print('==========') print(traceback.format_exc()) print('Locals by frame:') print('================') for frame in stack: print('Frame %s in %s at line %s' % (frame.f_code.co_name, frame.f_code.co_filename, frame.f_lineno)) for key, value in frame.f_locals.items(): print('%20s = %r' % (key, value)) print() print()
if __name__ == '__main__': import doctest def example(): """ A simple example where this approach comes in handy. Basically, we have a simple function which manipulates all the strings in a list. The function doesn't do any error checking, so when we pass a list which contains something other than strings, we get an error. """ data = ["1", "2", 3, "4"] # Typo: We 'forget' the quotes on data[2] def pad4(seq): """Pad each string in seq with zeros, to four places.""" return_value = [] for thing in seq: return_value.append("0" * (4 - len(thing)) + thing) # intensionally buggy return return_value print('============================================================') print('The usual information') print('============================================================') # First, show the information we get from a normal traceback.print_exc(). try: pad4(data) except: traceback.print_exc() print() print('============================================================') print('Tracebacks with the frame dump') print('============================================================') # With our new function it is to see the bad data that # caused the problem. The variable 'thing' has the value 3, so we know # that the TypeError we got was because of that. A quick look at the # value for 'data' shows us we simply forgot the quotes on that item. if 1: try: pad4(data) except: framedump() else: # pad4(data) framedump() example() import doctest doctest.testmod()