Source code for smpl_debug.debug

"""Simplified python debuging."""
import os
from inspect import currentframe, getsource

import numpy as np

# TODO comapare against logging module https://docs.python.org/3.5/library/logging.html

# TODO instead of hard coded global variables, use function arguments


# TODO Enum maybe?
# DEBUG_LEVEL=-1 no debug
# DEBUG_LEVEL= 0 default level
# DEBUG_LEVEL= 1,2,3 higher levels, more debug
DEBUG_LEVEL = -1


# print prefix
DEBUG_PRE = "DBG"

# remove folders of files for print

# filename strings with .py (with folder if SPLIT_FILENAME==False)
WHITE_LIST_FILES = []
BLACK_LIST_FILES = []

active = debug = on = DEBUG_LEVEL >= 0
# count debug events by file+line
count_times = {}
cur_table_line = {}


[docs]def get_frame(_back=0): cf = currentframe() for _ in range(_back + 1): cf = cf.f_back return cf
[docs]def once(_back=0): """ Returns true only one time Examples -------- >>> for i in range(10): ... if once(): ... print(i) 0 """ return times(1, _back=_back + 1)
[docs]def times(t=1, _back=0): """ Returns true if the count of the current line is greater than or equal to ``t``. Parameters ---------- t : int The count to check against. _back : int Number of stack/frames to go back. Returns ------- bool True if the count of the current line is greater than or equal to ``t``. Examples -------- >>> reset_times() >>> for i in range(10): ... if times(3): ... print(i) 0 1 2 """ line, fname = get_line_number_file(_back=_back + 1) inc_count(line, fname) return check_count(line, fname, t)
[docs]def get_line_src(_back=0): """ Gets the current line in the python source. Parameters ---------- _back : int Number of stack/frames to go back. Returns ------- src : str The current line in the python source. Examples -------- >>> get_line_src() 'get_line_src()' >>> "funky"+get_line_src() 'funky"funky"+get_line_src()' """ cf = get_frame(_back=_back + 1) srcline = getsource(cf).split("\n")[cf.f_lineno - cf.f_code.co_firstlineno].strip() return srcline
[docs]def get_line_number_file(split=True, _back=0): """ Gets the current filename and the current linenumber within it. Parameters ---------- split : bool Indicates whenever the folders above of the file should be included in the returned filename. _back : int Number of stack/frames to go back. Returns ------- filenumber : int First element in the return array filename : str Second element in the return array Examples -------- >>> get_line_number_file() (1, '<doctest smpl.debug.get_line_number_file[0]>') >>> for i in range(2): ... get_line_number_file() (2, '<doctest smpl.debug.get_line_number_file[1]>') (2, '<doctest smpl.debug.get_line_number_file[1]>') """ cf = get_frame(_back=_back + 1) fname = cf.f_code.co_filename if split: fname = cf.f_code.co_filename.split("/")[-1] return cf.f_lineno, fname
[docs]def get_line_number(_back=0): return get_line_number_file(_back=_back + 1)[0]
[docs]def line(msg_, tag="", level=0, times=-1, _back=0, **kwargs): msg(msg_, tag=tag, level=level, times=times, line_=True, _back=_back + 1, **kwargs)
# only once
[docs]def line1(msg_, tag="", level=0, times=1, _back=0, **kwargs): """ Just like :func:`line` but ``times`` set to 1. Examples -------- >>> for i in range(-2,2): ... line1(i,level=-1) DBG::<doctest smpl.debug.line1[0]>:2: line1(i,level=-1) = -2 """ msg1(msg_, tag=tag, level=level, times=times, line_=True, _back=_back + 1, **kwargs)
# counting functions
[docs]def get_count(line, fname): """ Returns the counts of the line. Parameters ---------- line : int The line in the python source of ``fname``. fname : str The filename. Returns ------- count : int The count of the current line. Examples -------- >>> get_count(1, "debug.py") 0 """ global count_times if fname + ":" + str(line) in count_times: return count_times[fname + ":" + str(line)] return 0
[docs]def inc_count(line, fname): """ Increments the count of the line. Parameters ---------- line : int The line in the python source of ``fname``. fname : str The filename. Examples -------- >>> inc_count(1, "debug.py") """ global count_times if fname + ":" + str(line) in count_times: count_times[fname + ":" + str(line)] += 1 else: count_times[fname + ":" + str(line)] = 1
[docs]def check_count(line, fname, t): """ Returns true if the count of the line is greater than or equal to ``t``. Parameters ---------- line : int The line in the python source of ``fname``. fname : str The filename. t : int The count to check against. Returns ------- bool True if the count of the line is greater than or equal to ``t``. Examples -------- >>> check_count(2, "debug.py", 0) True >>> inc_count(2, "debug.py") >>> check_count(2, "debug.py", 0) False """ if t >= get_count(line, fname) or t == -1: if fname not in BLACK_LIST_FILES and ( len(WHITE_LIST_FILES) == 0 or fname in WHITE_LIST_FILES ): return True return False
# _line enables printint src line # t stands for times
[docs]def msg(msg, tag="", level=0, times=-1, line_=False, _back=0, **kwargs): """ Prints the message ``msg`` if level > debug_level and always returns the msg. Parameters ---------- tag : str Sets a tag to be printed for the debug message. level : int Debug level. times : int How often should the message be printed if the function gets called multiple times (e.g. in a loop). _line : bool Print the current line in the python source. _back : int Number of stack/frames to go back. Examples -------- >>> msg("hi", level = -9999) DBG::<doctest smpl.debug.msg[0]>:1: hi 'hi' >>> msg("hi") 'hi' """ if level <= DEBUG_LEVEL: line, fname = get_line_number_file(_back=_back + 1) src = "" if line_: src = get_line_src(_back=_back + 1) src = src + " = " inc_count(line, fname) if check_count(line, fname, times): print( DEBUG_PRE + ":" + tag + ":" + fname + ":" + str(line) + ": " + src + str(msg) ) return msg
[docs]def msg1(_msg, tag="", level=0, times=1, line_=False, _back=0, **kwargs): """ Just like :func:`msg` but ``times`` set to 1. Parameters ---------- tag : str Sets a tag to be printed for the debug message. level : int Debug level. times : int How often should the message be printed if the function gets called multiple times (e.g. in a loop). _line : bool Print the current line in the python source. _back : int Number of stack/frames to go back. Examples -------- >>> for i in range(-2,2): ... msg1(i, level = i) DBG::<doctest smpl.debug.msg1[0]>:2: -2 -2 -1 0 1 """ return msg( _msg, level=level, tag=tag, times=times, line_=line_, _back=_back + 1, **kwargs )
[docs]def file( key, value, level=0, times=-1, seperator=";", _print=True, _back=0, filename="debug.csv", ): """ Prints the message ``msg`` if level > debug_level to file ``filename``. """ if level <= DEBUG_LEVEL: line, fname = get_line_number_file(_back=_back + 1) inc_count(line, fname) if check_count(line, fname, times): f = open(filename, "a+") if _print: tag = "debug.file" print( DEBUG_PRE + ":" + tag + ":" + fname + ":" + str(line) + ": " + key + seperator + value ) f.write(key + seperator + value + "\n") f.close() return value
[docs]def file1(_key, _value, level=0, times=1, _back=0, **kwargs): """ Just like :func:`file` but ``times`` set to 1. """ return file(_key, _value, level=level, times=times, _back=_back + 1, **kwargs)
[docs]def table_flush_header(filename="debug_table.csv", seperator=";"): """ Saves the current keys from :func:`table` to ``filename``. """ global cur_table_line f = open(filename, "a+") for key in sorted(cur_table_line): f.write(key + seperator) f.write("\n") f.close()
[docs]def table_flush_line(filename="debug_table.csv", seperator=";"): """ Saves the current values from :func:`table` to ``filename`` """ global cur_table_line f = open(filename, "a+") ok = False dim = 1 for cur in iter(cur_table_line): ok = isinstance(cur_table_line[cur], np.ndarray) if ok: dim = len(cur_table_line[cur]) break for i in range(dim): for key in sorted(cur_table_line): if isinstance(cur_table_line[key], np.ndarray): f.write("%.30e" % cur_table_line[key][i] + seperator) else: f.write("%.30e" % cur_table_line[key] + seperator) f.write("\n") f.close()
[docs]def table( key, value, level=0, times=-1, seperator=";", _print=False, _back=0, filename="debug_table.csv", ): """ Saves ``key``:``value`` in ``filename``. Examples -------- >>> for i in range(-2,2): ... table("a", i,level=-1) ... table("b", i**2,level=-1) ... table("c", i**i,level=-1) ... if once(): table_flush_header(); ... table_flush_line() -2 4 0.25 -1 1 -1.0 0 0 1 1 1 1 >>> from smpl import io >>> print(io.read("debug_table.csv").strip()) a;b;c; -2.000000000000000000000000000000e+00;4.000000000000000000000000000000e+00;2.500000000000000000000000000000e-01; -1.000000000000000000000000000000e+00;1.000000000000000000000000000000e+00;-1.000000000000000000000000000000e+00; 0.000000000000000000000000000000e+00;0.000000000000000000000000000000e+00;1.000000000000000000000000000000e+00; 1.000000000000000000000000000000e+00;1.000000000000000000000000000000e+00;1.000000000000000000000000000000e+00; >>> import pandas as pd >>> pd.read_csv("debug_table.csv") a;b;c; 0 -2.000000000000000000000000000000e+00;4.000000... 1 -1.000000000000000000000000000000e+00;1.000000... 2 0.000000000000000000000000000000e+00;0.0000000... 3 1.000000000000000000000000000000e+00;1.0000000... >>> reset_table() >>> io.write("debug_table.csv","") >>> for i in range(1,3): ... table("a", np.array([i*k for k in range(5)]),level=-1) ... table("b", np.array([i*i*k for k in range(5)]),level=-1) ... if once(): table_flush_header(); ... table_flush_line() array([0, 1, 2, 3, 4]) array([0, 1, 2, 3, 4]) array([0, 2, 4, 6, 8]) array([ 0, 4, 8, 12, 16]) >>> print(io.read("debug_table.csv").strip()) a;b; 0.000000000000000000000000000000e+00;0.000000000000000000000000000000e+00; 1.000000000000000000000000000000e+00;1.000000000000000000000000000000e+00; 2.000000000000000000000000000000e+00;2.000000000000000000000000000000e+00; 3.000000000000000000000000000000e+00;3.000000000000000000000000000000e+00; 4.000000000000000000000000000000e+00;4.000000000000000000000000000000e+00; 0.000000000000000000000000000000e+00;0.000000000000000000000000000000e+00; 2.000000000000000000000000000000e+00;4.000000000000000000000000000000e+00; 4.000000000000000000000000000000e+00;8.000000000000000000000000000000e+00; 6.000000000000000000000000000000e+00;1.200000000000000000000000000000e+01; 8.000000000000000000000000000000e+00;1.600000000000000000000000000000e+01; """ global cur_table_line if level <= DEBUG_LEVEL: line, fname = get_line_number_file(_back=_back + 1) inc_count(line, fname) if check_count(line, fname, times): if isinstance(value, np.ndarray): cur_table_line[key] = value.copy() else: cur_table_line[key] = value return value
[docs]def reset_times(): """ Resets global `count_times`. """ global count_times count_times = {}
[docs]def reset_table(): """ Resets global `cur_table_line`. """ global cur_table_line cur_table_line = {}
reset_count = reset_times if os.path.exists("debug.csv"): os.remove("debug.csv") if os.path.exists("debug_table.csv"): os.remove("debug_table.csv") if __name__ == "__main__": import doctest doctest.testmod(verbose=True, optionflags=doctest.ELLIPSIS)