from box.timer import Timer timer = Timer(precision=3) from typing import TYPE_CHECKING # takes 0.01s if TYPE_CHECKING: import pdb import sys from typing import Optional, List from contextlib import contextmanager from types import FrameType, TracebackType from box.json_decode_error import handle_json_decode_error # color_arg, color_error_repr, print_error_information {{{ def color_arg(arg): BLACK = '\x1b[30m' RED = '\x1b[31m' GREEN = '\x1b[32m' YELLOW = '\x1b[33m' BLUE = '\x1b[34m' MAGENTA = '\x1b[35m' CYAN = '\x1b[36m' WHITE = '\x1b[37m' LIGHTBLACK_EX = '\x1b[90m' LIGHTRED_EX = '\x1b[91m' LIGHTGREEN_EX = '\x1b[92m' LIGHTYELLOW_EX = '\x1b[93m' LIGHTBLUE_EX = '\x1b[94m' LIGHTMAGENTA_EX = '\x1b[95m' LIGHTCYAN_EX = '\x1b[96m' LIGHTWHITE_EX = '\x1b[97m' RESET = '\x1b[39m' if type(arg) == str: arg = YELLOW + repr(arg) else: arg = CYAN + repr(arg) return arg def color_error_repr(error): BLACK = '\x1b[30m' RED = '\x1b[31m' GREEN = '\x1b[32m' YELLOW = '\x1b[33m' BLUE = '\x1b[34m' MAGENTA = '\x1b[35m' CYAN = '\x1b[36m' WHITE = '\x1b[37m' LIGHTBLACK_EX = '\x1b[90m' LIGHTRED_EX = '\x1b[91m' LIGHTGREEN_EX = '\x1b[92m' LIGHTYELLOW_EX = '\x1b[93m' LIGHTBLUE_EX = '\x1b[94m' LIGHTMAGENTA_EX = '\x1b[95m' LIGHTCYAN_EX = '\x1b[96m' LIGHTWHITE_EX = '\x1b[97m' BOLD = '\x1b[1m' RESET = '\x1b[39m' RESET_ALL = '\x1b[0m' NORMAL = '\x1b[22m' string = repr(error) args = ', '.join(color_arg(arg) for arg in error.args) colored_string = BOLD + f"{BOLD + LIGHTRED_EX}{error.__class__.__name__}{NORMAL}({YELLOW}{args}{LIGHTRED_EX}){RESET}" return colored_string # lhs = Code.LIGHTRED_EX + m.group(1) # middle = Code.YELLOW + m.group(2) # # middle = re.sub(r"('\w+')", Code.LIGHTCYAN_EX + r'\1', middle) # rhs = Code.LIGHTRED_EX + m.group(3) # colored_string = lhs + middle + rhs return colored_string def print_error_information(error: Exception, frame_to_green: FrameType): # C:\Users\vivek\Documents\Python -> ~/Documents/Python # C:\Users\vivek\AppData\Roaming\Python\Python310\site-packages -> $APPDATA/Python/site-packages # C:\Program Files\Python310\Lib\site-packages -> import traceback import os from pathlib import Path tb = error.__traceback__ print() print(color_error_repr(error)) # print(Code.LIGHTRED_EX + repr(error)) from box.frame import frame_gen from box.color import Code call_frames = list(frame_gen()) call_file = call_frames[3].f_code.co_filename frames = [ frame for frame, _ in traceback.walk_tb(tb) ] summaries = traceback.extract_tb(tb) pairs = reversed(list(zip(frames, summaries))) from box.ic import Table table = Table() table.gutter = ' ' table.separator = ' ... ' for i, (frame, summary) in enumerate(pairs): filepath = summary.filename filepath = str(Path(filepath).resolve()) # C:\users -> C:\Users if os.getcwd() in filepath: filepath = os.path.relpath(os.path.abspath(filepath)) else: homedir = os.path.expanduser("~") filepath = filepath.replace(homedir, "~") line_number = summary.lineno line = summary.line # line = Code.LIGHTGREEN_EX + line if i == 0 else line # if green_done is False and frame.f_code.co_filename == call_file: if frame == frame_to_green: line = Code.LIGHTGREEN_EX + line line = '\x1b[1m' + line + '\x1b[22m' green_done = True lhs_string = f"{filepath}:{line_number}" table.row([lhs_string, line]) table_string = str(table) table_lines = table_string.split("\n") table_lines = table_lines[1:] table_string = "\n".join(table_lines) print(table_string) # }}} def framestr(frame: FrameType) -> str: with open(frame.f_code.co_filename) as f: lines = f.readlines() line = lines[frame.f_lineno - 1].strip() return f' {frame.f_code.co_filename}:{frame.f_lineno} {line}' def _select_frame_silent(self: "pdb.Pdb", number: int): assert 0 <= number < len(self.stack) self.curindex = number self.curframe = self.stack[self.curindex][0] self.curframe_locals = self.curframe.f_locals self.lineno = None def do_down_silent(self: "pdb.Pdb", arg: int): if self.curindex + 1 == len(self.stack): print('Newest frame') return count = arg if count < 0: newframe = len(self.stack) - 1 else: newframe = min(len(self.stack) - 1, self.curindex + count) _select_frame_silent(self, newframe) def do_up_silent(self: "pdb.Pdb", error: Exception, arg: int): if self.curindex == 0: print('Oldest frame') return count = arg if count < 0: newframe = 0 else: newframe = max(0, self.curindex - count) _select_frame_silent(self, newframe) def x_three(self: "pdb.Pdb", error: Exception, arg): """ Move up the stack by `arg` frames and print the stack frame """ do_up_silent(self, error, arg) assert self.curframe print_error_information(error, self.curframe) def make_x_one(self: "pdb.Pdb", error: Exception): def x_one(arg): if arg == '': arg = 1 else: arg = int(arg) x_three(self, error, arg) return x_one def y_three(self: "pdb.Pdb", error: Exception, arg): """ Move down the stack by `arg` frames and print the stack frame """ do_down_silent(self, arg) assert self.curframe print_error_information(error, self.curframe) def make_y_one(self: "pdb.Pdb", error: Exception): def y_one(arg): if arg == '': arg = 1 else: arg = int(arg) y_three(self, error, arg) return y_one def make_v_zero_arg(self: "pdb.Pdb", error: Exception): """ Print the variables in the current stack frame """ from box.error import print_variables def v_zero_arg(*args): assert self.curframe print_variables(self.curframe.f_locals) return v_zero_arg def make_z_zero_arg(self: "pdb.Pdb", error: Exception): """ Print the stack frame """ def z_zero_arg(*args): assert self.curframe print_error_information(error, self.curframe) return z_zero_arg def ic_two(self: "pdb.Pdb", arg): from box.ic import ic assert self.curframe val = eval(arg, self.curframe.f_globals, self.curframe.f_locals) ic(val) def make_ic_one_arg(self: "pdb.Pdb"): def ic_one(arg): ic_two(self, arg) return ic_one def ib_two(self: "pdb.Pdb", arg): from box.ic import ib assert self.curframe val = eval(arg, self.curframe.f_globals, self.curframe.f_locals) ib(val) def make_ib_one_arg(self: "pdb.Pdb"): def ib_one(arg): ib_two(self, arg) return ib_one def print_error_information_from_pdb(self: "pdb.Pdb", error: Exception): # frame = self.curframe # assert frame if not self.curframe: raise ValueError('No frame selected') print_error_information(error, self.curframe) @contextmanager def handler(): from json import JSONDecodeError # takes 0.01s - 0.02s import traceback # takes 0.002s, not much but still inlining try: yield except Exception as error: if isinstance(error, JSONDecodeError): handle_json_decode_error(error) if type(error).__name__ == 'bdb.BdbQuit': pass else: # pdb.post_mortem(error.__traceback__); return import pdb caller_file = sys._getframe(2).f_code.co_filename Pdb = pdb.Pdb() Pdb.reset() # CUSTOMIZATION 2: Move the debugger to the frame where the error occurred within the file from which the program was run, instead of the frame where the error was raised frames = [ frame for frame, _ in traceback.walk_tb(error.__traceback__) ] # needle_frame = next(frame for frame in reversed(frames) if frame.f_code.co_filename == __file__) needle_frame = next(frame for frame in reversed(frames) if frame.f_code.co_filename == caller_file) Pdb.setup(f=None, tb=error.__traceback__) # NOTE[1/1]: Do not do `Pdb.setup(f=needle_frame, tb=error.__traceback__)` as it creates new frames or something _select_frame_silent(Pdb, frames.index(needle_frame)) # CUSTOMIZATION 3: Add `x` and `y` commands for moving up and down the stack in a customized way, and `z` command for printing variables Pdb.do_x = make_x_one(Pdb, error) # pyright:ignore[reportAttributeAccessIssue] Pdb.do_y = make_y_one(Pdb, error) # pyright:ignore[reportAttributeAccessIssue] Pdb.do_z = make_z_zero_arg(Pdb, error) # pyright:ignore[reportAttributeAccessIssue] Pdb.do_v = make_v_zero_arg(Pdb, error) # pyright:ignore[reportAttributeAccessIssue] # CUSTOMIZATION 1: Add `ic` and `ib` aliases for exploration Pdb.do_ic = make_ic_one_arg(Pdb) # pyright:ignore[reportAttributeAccessIssue] Pdb.do_ib = make_ib_one_arg(Pdb) # pyright:ignore[reportAttributeAccessIssue] # CUSTOMIZATION 4: Nicer stacktrace print_error_information_from_pdb(Pdb, error) Pdb._cmdloop() def select_frame_for_file(frames: List[FrameType], target_file: str) -> Optional[FrameType]: for frame in reversed(frames): if frame.f_code.co_filename == target_file: return frame return None def raise_simple_json_decode_error(): import json string = '' json.loads(string) raise json.JSONDecodeError('Expecting value', '', 0) def main(): # files: # [1] json_decode_error.py raise_simple_json_decode_error() # raise Exception('This is an error', 1) if __name__ == "__main__": with handler(): main() # run.vim: vert term python box/__init__.py