2Functions & classes that can be useful for debugging
6from inspect
import stack
8import inspect
as _inspect
9from os
import get_terminal_size
10from os.path
import relpath
12from .colors
import parse_color
13from re
import match
as re_match
14from typing
import Union, Literal
15from varname
import VarnameRetrievingError, argname, nameof
16from pprint
import pformat
20from reprlib
import Repr
23 from traceback_with_variables
import activate_by_import
24except ImportError:
pass
27Log = logging.getLogger(__name__)
36def printArgs(*args, **kwargs):
38 print(
'kwargs:', kwargs)
40def get_metadata(calls:int=1) -> inspect.FrameInfo:
41 """ Gets the meta data of the line you're calling this function from.
42 `calls` is for how many function calls to look back
from.
43 Returns
None if that number of calls
is invalid
52 iterable: Union[tuple, list, set, dict],
53 method: Literal[
'pprint',
'custom']=
'custom',
58 """ "Cast" a tuple, list, set or dict to a string, automatically shorten
59 it if it
's long, and display how long it is.
62 limitToLine: if True, limit the length of list to a single line
63 minItems: show at least this many items
in the list
64 maxItems: show at most this many items
in the list
65 color: a simple int color
68 If limitToLine
is True, it will overrule maxItems, but *
not* minItems
71 width == get_terminal_size().columns
73 if method ==
'pprint':
74 return pformat(iterable, width=width, depth=depth, indent=indent)
75 elif method ==
'custom':
76 def getBrace(opening):
77 if isinstance(iterable, list):
78 return '[' if opening
else ']'
79 elif isinstance(iterable, (set, dict)):
80 return '{' if opening
else '}'
82 return '(' if opening
else ')'
84 lengthAddOn = f
'(len={len(iterable)})'
85 defaultStr = str(iterable)
89 if len(defaultStr) + len(lengthAddOn) > (get_terminal_size().columns / 2):
90 rtnStr = f
'{lengthAddOn} {getBrace(True)}'
91 if isinstance(iterable, dict):
92 for key, val
in iterable.items():
93 rtnStr += f
'\n\t<{type(key).__name__}> {key}: <{type(val).__name__}> {val}'
95 for cnt, i
in enumerate(iterable):
96 rtnStr += f
'\n\t{cnt}: <{type(i).__name__}> {i}'
99 rtnStr += getBrace(
False)
101 rtnStr = defaultStr + lengthAddOn
105 raise TypeError(f
"Incorrect method `{method}` given. Options are 'pprint' and 'custom'.")
108 """ Get the name of the type of var, formatted nicely.
109 Also gets the types of the elements it holds, if `var`
is a collection.
111 def getUniqueType(item):
112 returnMe = type(item).__name__
113 while isinstance(item, (tuple, list, set)):
116 except (KeyError, IndexError, TypeError):
119 returnMe +=
'(' + type(item).__name__
125 return returnMe + (
')'*cnt)
127 if isinstance(var, dict):
128 name = type(var).__name__
130 rtn = f
'{name}({type(list(var.keys())[0]).__name__}:{type(list(var.values())[0]).__name__})'
133 elif isinstance(var, (tuple, list, set, dict)):
136 types.append(getUniqueType(i))
137 types = sorted(set(types), key=
lambda x: types.index(x))
138 fullName = type(var).__name__ + str(tuple(types)).replace(
"'",
"")
140 fullName = fullName[:-2] +
')'
143 rtn = type(var).__name__
144 return f
'<{rtn}>' if add_braces
else rtn
146def print_debug_count(left_adjust:int=2):
147 """ Increment and print the debug count """
150 print(f
'{str(_debug_count)+":":<{left_adjust+2}}', end=
'', style=
'count')
152def get_varname(var, full:bool=
True, calls:int=1, metadata:inspect.FrameInfo=
None) -> str:
153 """ Gets the variable name given to `var` """
154 def get_varname_manually(calls):
156 context = get_metadata(calls=calls+2).code_context
163 match = re_match(
r"(?:.+)?debug\((.+(?:\((?:.+)?\))*)\)(?:.+)?", line)
167 return match.groups()[0]
172 rtn = argname(
'var', frame=calls+1)
174 except Exception
as e:
179 rtn = nameof(var, frame=calls+1, vars_only=
False)
180 except Exception
as e2:
181 if verbosity >= 2
and not isinstance(var, Exception):
184 rtn = get_varname_manually(calls+1)
185 except VarnameRetrievingError
as e:
189 rtn = get_varname_manually(calls+1)
193 if rtn == str(var)
or rtn == f
"'{var}'":
202 """ Gets the filename of the file given, adjusted to be relative to the appropriate root directory """
205 return relpath(filename)
207 return relpath(filename, root_dir)
211def get_context(metadata:inspect.FrameInfo, func:bool=
None, file:bool=
None, path:bool=
None) -> str:
212 """ Returns the stuff in the [] (the "context") """
216 func = display_func
if func
is None else (display_func
or func)
217 file = display_file
if file
is None else (display_file
or file)
218 path = display_path
if path
is None else (display_path
or path)
226 s += f
'"{metadata.filename}", line {metadata.lineno}, in '
228 s += f
'"{get_adjusted_filename(metadata.filename)}", line {metadata.lineno}, in '
234 if metadata.function.startswith(
'<'):
237 s += f
'{metadata.function}()'
244def print_stack_trace(calls, func, file, path):
245 for i
in reversed(stack()[3:]):
246 print(
'\t', get_context(i, func, file, path), style=
'trace')
249def called_as_decorator(funcName, metadata=None, calls=1) -> 'Union[1, 2, 3, False]':
250 """ Return 1 if being used as a function decorator, 2 if as a class decorator, 3 if not sure, and False if neither. """
252 metadata = get_metadata(calls+1)
255 context = metadata.code_context
261 if funcName
not in line:
264 elif 'class ' in line:
273def print_context(calls:int=1, func:bool=
True, file:bool=
True, path:bool=
False, color=
'context'):
275 print(get_context(get_metadata(1 + calls)), end=
'', style=color)
285 show: Literal[
'pprint',
'custom',
'repr']=
'custom',
292 background: bool=
False,
298 stackTrace: bool=
False,
299 raiseError: bool=
False,
304 throwError: bool=
False,
307 """ Print variable names and values for easy debugging.
310 debug() -> Prints a standard message to just tell you that it's getting called
311 debug('msg') -> Prints the string along
with metadata
312 debug(var) -> Prints the variable name, type,
and value
313 foo = debug(bar) -> Prints the variable name, type,
and value,
and returns the variable
314 @debug -> Use
as a decorator to make note of when the function
is called
317 var: The variable
or variables to
print
318 name: Manully specify the name of the variable
319 color: A number between 0-5,
or 3
or 4 tuple/list of color data to
print the debug message
as
320 func: Ensure that the function the current call
is called
from is shown
321 file: Ensure that the file the current call
is called
from is shown
322 path: Show the full path of the current file, isntead of it
's relative path
323 useRepr: Use __repr__() instead of __str__() on the given variable
324 limitToLine: If var is a list/tuple/dict/set, only show
as many items
as will fit on one terminal line, overriden by minItems
325 minItems: If var
is a list/tuple/dict/set, don
't truncate more than this many items
326 maxItems: If var is a list/tuple/dict/set, don
't show more than this many items
327 stackTrace: Prints a neat stack trace of the current call
328 calls: If you're passing in a return from a function, say calls=2
329 background: Changes the background color instead of the forground color
330 active: Conditionally disables the function
332 _repr: Alias of useRepr
333 trace: Alias of stackTrace
334 bg: Alias of background
336 if not active
or not __debug__:
344 stackTrace = stackTrace
or trace
346 background = background
or bg
347 throwError = throw
or throwError
or raiseError
348 file = file
or display_file
349 func = func
or display_func
350 path = path
or display_path
351 useColor = (
'default' if clr
is Ellipsis
else clr)
if color
is Ellipsis
else color
353 if isinstance(var, Warning):
355 elif isinstance(var, Exception):
362 metadata = get_metadata(calls+1)
364 _print_context =
lambda: print(get_context(metadata, func, file, path), end=
'', style=
'context')
368 if callable(var)
and called_as_decorator(
'debug', metadata):
369 def wrap(*args, **kwargs):
371 metadata = get_metadata(2)
375 print_stack_trace(2, func, file, path)
378 print(f
'{var.__name__}() called!', style=
'note')
379 return var(*args, **kwargs)
386 print_stack_trace(calls+1, func, file, path)
393 if not metadata.function.startswith(
'<'):
394 print(f
'{metadata.function}() called ', end=
'', style=useColor)
396 print(
'HERE!', style=useColor)
408 if isinstance(var, (tuple, list, set, dict)):
414 varName = get_varname(var, calls=calls, metadata=metadata)
if name
is None else name
418 print(
'<literal> ', end=
'', style=
'type')
419 print(var, style=
'value')
422 print(varType, end=
' ', style=
'type')
423 print(varName, end=
' ', style=
'name')
424 print(
'=', end=
' ', style=
'equals')
425 print(varVal, style=
'value')
427 if isinstance(var, Exception)
and throwError:
435 Log.setLevel(logging.DEBUG)
450 """ Print variable names and values for easy debugging.
453 debug() -> Prints a standard message to just tell you that it's getting called
454 debug('msg') -> Prints the string along
with metadata
455 debug(var) -> Prints the variable name, type,
and value
456 foo = debug(bar) -> Prints the variable name, type,
and value,
and returns the variable
457 @debug -> Use
as a decorator to make note of when the function
is called
460 var: The variable
or variables to
print
461 name: Manully specify the name of the variable
462 color/clr: Literally anything that specifies a color, including a single number
for unique colors
463 inspect: Calls rich.inspect on var
464 repr: Uses repr by default, set to
False to use str instead
465 trace: Prints a neat stack trace of the current call
466 calls: If you
're passing in a return from a function, say calls=2
467 active: Conditionally disables the function
470 if not active
or not Log.isEnabledFor(logging.DEBUG):
475 if hasattr(var,
'__call__'):
478 print(
"Called as a decorator")
481 print(
"Called directly")
497 stackTrace = stackTrace
or trace
499 background = background
or bg
500 throwError = throw
or throwError
or raiseError
501 file = file
or display_file
502 func = func
or display_func
503 path = path
or display_path
504 useColor = (
'default' if clr
is Ellipsis
else clr)
if color
is Ellipsis
else color
506 if isinstance(var, Warning):
508 elif isinstance(var, Exception):
515 metadata = get_metadata(calls+1)
517 _print_context =
lambda: print(get_context(metadata, func, file, path), end=
'', style=
'context')
521 if callable(var)
and called_as_decorator(
'debug', metadata):
522 def wrap(*args, **kwargs):
524 metadata = get_metadata(2)
528 print_stack_trace(2, func, file, path)
531 print(f
'{var.__name__}() called!', style=
'note')
532 return var(*args, **kwargs)
539 print_stack_trace(calls+1, func, file, path)
546 if not metadata.function.startswith(
'<'):
547 print(f
'{metadata.function}() called ', end=
'', style=useColor)
549 print(
'HERE!', style=useColor)
561 if isinstance(var, (tuple, list, set, dict)):
567 varName = get_varname(var, calls=calls, metadata=metadata)
if name
is None else name
571 print(
'<literal> ', end=
'', style=
'type')
572 print(var, style=
'value')
575 print(varType, end=
' ', style=
'type')
576 print(varName, end=
' ', style=
'name')
577 print(
'=', end=
' ', style=
'equals')
578 print(varVal, style=
'value')
580 if isinstance(var, Exception)
and throwError:
590def debug(var=undefined, name=None, color=1, trace=False, calls=1):
591 r, g, b = parse_color(color)
593 metadata = get_metadata(calls+1)
594 _print_context =
lambda: print(
'[dark gray]' + str(get_context(metadata,
True,
True,
True)) +
'[/]', end=
'')
615 if isinstance(var, (tuple, list, set, dict)):
621 varName = name
or get_varname(var, calls=calls, metadata=metadata)
625 print(
'[type]',
'<literal> ', end=
'')
626 print(
'[value]', var)
629 print(
'[type]', varType, end=
' ')
630 print(
'[name]', varName, end=
' ')
631 print(
'[equals]',
'=', end=
' ')
632 print(
'[value]', varVal)
"var" __call__(self, var=undefined, str name=None, color=..., bool inspect=False, bool repr=True, bool trace=False, bool throw=False, int calls=1, bool active=True, clr=...)
Print variable names and values for easy debugging.
str get_full_typename(var, bool add_braces=True)
Get the name of the type of var, formatted nicely.
str prettify(Union[tuple, list, set, dict] iterable, Literal['pprint', 'custom'] method='custom', int width=..., int depth=..., int indent=4)
"Cast" a tuple, list, set or dict to a string, automatically shorten it if it's long,...
str get_adjusted_filename(str filename)
Gets the filename of the file given, adjusted to be relative to the appropriate root directory.