2222StrType = str
2323
2424
25+ class DebugFrame :
26+ __slots__ = 'function' , 'path' , 'lineno'
27+
28+ @staticmethod
29+ def from_call_frame (call_frame : 'FrameType' ) -> 'DebugFrame' :
30+ from pathlib import Path
31+
32+ function = call_frame .f_code .co_name
33+
34+ path = Path (call_frame .f_code .co_filename )
35+ if path .is_absolute ():
36+ # make the path relative
37+ cwd = Path ('.' ).resolve ()
38+ try :
39+ path = path .relative_to (cwd )
40+ except ValueError :
41+ # happens if filename path is not within CWD
42+ pass
43+
44+ lineno = call_frame .f_lineno
45+
46+ return DebugFrame (function , str (path ), lineno )
47+
48+ def __init__ (self , function : str , path : str , lineno : int ):
49+ self .function = function
50+ self .path = path
51+ self .lineno = lineno
52+
53+ def __str__ (self ) -> StrType :
54+ return self .str ()
55+
56+ def str (self , highlight : bool = False ) -> StrType :
57+ if highlight :
58+ return (
59+ f'{ sformat (self .path , sformat .magenta )} :{ sformat (self .lineno , sformat .green )} '
60+ f'{ sformat (self .function , sformat .green , sformat .italic )} '
61+ )
62+ else :
63+ return f'{ self .path } :{ self .lineno } { self .function } '
64+
65+
2566class DebugArgument :
2667 __slots__ = 'value' , 'name' , 'extra'
2768
@@ -66,43 +107,37 @@ class DebugOutput:
66107 """
67108
68109 arg_class = DebugArgument
69- __slots__ = 'filename' , 'lineno' , 'frame ' , 'arguments' , 'warning'
110+ __slots__ = 'call_context ' , 'arguments' , 'warning'
70111
71112 def __init__ (
72113 self ,
73114 * ,
74- filename : str ,
75- lineno : int ,
76- frame : str ,
115+ call_context : 'List[DebugFrame]' ,
77116 arguments : 'List[DebugArgument]' ,
78117 warning : 'Union[None, str, bool]' = None ,
79118 ) -> None :
80- self .filename = filename
81- self .lineno = lineno
82- self .frame = frame
119+ self .call_context = call_context
83120 self .arguments = arguments
84121 self .warning = warning
85122
86123 def str (self , highlight : bool = False ) -> StrType :
87- if highlight :
88- prefix = (
89- f'{ sformat (self .filename , sformat .magenta )} :{ sformat (self .lineno , sformat .green )} '
90- f'{ sformat (self .frame , sformat .green , sformat .italic )} '
91- )
92- if self .warning :
124+ prefix = '\n ' .join (x .str (highlight ) for x in self .call_context )
125+
126+ if self .warning :
127+ if highlight :
93128 prefix += sformat (f' ({ self .warning } )' , sformat .dim )
94- else :
95- prefix = f'{ self .filename } :{ self .lineno } { self .frame } '
96- if self .warning :
129+ else :
97130 prefix += f' ({ self .warning } )'
98- return f'{ prefix } \n ' + '\n ' .join (a .str (highlight ) for a in self .arguments )
131+
132+ return prefix + '\n ' + '\n ' .join (a .str (highlight ) for a in self .arguments )
99133
100134 def __str__ (self ) -> StrType :
101135 return self .str ()
102136
103137 def __repr__ (self ) -> StrType :
138+ context = self .call_context [- 1 ]
104139 arguments = ' ' .join (str (a ) for a in self .arguments )
105- return f'<DebugOutput { self . filename } :{ self .lineno } { self . frame } arguments: { arguments } >'
140+ return f'<DebugOutput { context . path } :{ context .lineno } { context . function } arguments: { arguments } >'
106141
107142
108143class Debug :
@@ -118,9 +153,10 @@ def __call__(
118153 file_ : 'Any' = None ,
119154 flush_ : bool = True ,
120155 frame_depth_ : int = 2 ,
156+ trace_ : bool = False ,
121157 ** kwargs : 'Any' ,
122158 ) -> 'Any' :
123- d_out = self ._process (args , kwargs , frame_depth_ )
159+ d_out = self ._process (args , kwargs , frame_depth_ , trace_ )
124160 s = d_out .str (use_highlight (self ._highlight , file_ ))
125161 print (s , file = file_ , flush = flush_ )
126162 if kwargs :
@@ -130,8 +166,25 @@ def __call__(
130166 else :
131167 return args
132168
133- def format (self , * args : 'Any' , frame_depth_ : int = 2 , ** kwargs : 'Any' ) -> DebugOutput :
134- return self ._process (args , kwargs , frame_depth_ )
169+ def trace (
170+ self ,
171+ * args : 'Any' ,
172+ file_ : 'Any' = None ,
173+ flush_ : bool = True ,
174+ frame_depth_ : int = 2 ,
175+ ** kwargs : 'Any' ,
176+ ) -> 'Any' :
177+ return self .__call__ (
178+ * args ,
179+ file_ = file_ ,
180+ flush_ = flush_ ,
181+ frame_depth_ = frame_depth_ + 1 ,
182+ trace_ = True ,
183+ ** kwargs ,
184+ )
185+
186+ def format (self , * args : 'Any' , frame_depth_ : int = 2 , trace_ : bool = False , ** kwargs : 'Any' ) -> DebugOutput :
187+ return self ._process (args , kwargs , frame_depth_ , trace_ )
135188
136189 def breakpoint (self ) -> None :
137190 import pdb
@@ -141,38 +194,24 @@ def breakpoint(self) -> None:
141194 def timer (self , name : 'Optional[str]' = None , * , verbose : bool = True , file : 'Any' = None , dp : int = 3 ) -> Timer :
142195 return Timer (name = name , verbose = verbose , file = file , dp = dp )
143196
144- def _process (self , args : 'Any' , kwargs : 'Any' , frame_depth : int ) -> DebugOutput :
197+ def _process (self , args : 'Any' , kwargs : 'Any' , frame_depth : int , trace : bool ) -> DebugOutput :
145198 """
146199 BEWARE: this must be called from a function exactly `frame_depth` levels below the top of the stack.
147200 """
148201 # HELP: any errors other than ValueError from _getframe? If so please submit an issue
149202 try :
150203 call_frame : 'FrameType' = sys ._getframe (frame_depth )
151204 except ValueError :
152- # "If [ValueError] is deeper than the call stack, ValueError is raised"
205+ # "If [the given frame depth] is deeper than the call stack,
206+ # ValueError is raised"
153207 return self .output_class (
154- filename = '<unknown>' ,
155- lineno = 0 ,
156- frame = '' ,
208+ call_context = [DebugFrame (function = '' , path = '<unknown>' , lineno = 0 )],
157209 arguments = list (self ._args_inspection_failed (args , kwargs )),
158210 warning = self ._show_warnings and 'error parsing code, call stack too shallow' ,
159211 )
160212
161- function = call_frame .f_code .co_name
162-
163- from pathlib import Path
164-
165- path = Path (call_frame .f_code .co_filename )
166- if path .is_absolute ():
167- # make the path relative
168- cwd = Path ('.' ).resolve ()
169- try :
170- path = path .relative_to (cwd )
171- except ValueError :
172- # happens if filename path is not within CWD
173- pass
213+ call_context = _make_call_context (call_frame , trace )
174214
175- lineno = call_frame .f_lineno
176215 warning = None
177216
178217 import executing
@@ -183,17 +222,15 @@ def _process(self, args: 'Any', kwargs: 'Any', frame_depth: int) -> DebugOutput:
183222 arguments = list (self ._args_inspection_failed (args , kwargs ))
184223 else :
185224 ex = source .executing (call_frame )
186- function = ex .code_qualname ()
225+ call_context [ - 1 ]. function = ex .code_qualname ()
187226 if not ex .node :
188227 warning = 'executing failed to find the calling node'
189228 arguments = list (self ._args_inspection_failed (args , kwargs ))
190229 else :
191230 arguments = list (self ._process_args (ex , args , kwargs ))
192231
193232 return self .output_class (
194- filename = str (path ),
195- lineno = lineno ,
196- frame = function ,
233+ call_context = call_context ,
197234 arguments = arguments ,
198235 warning = self ._show_warnings and warning ,
199236 )
@@ -225,4 +262,18 @@ def _process_args(self, ex: 'Any', args: 'Any', kwargs: 'Any') -> 'Generator[Deb
225262 yield self .output_class .arg_class (value , name = name , variable = kw_arg_names .get (name ))
226263
227264
265+ def _make_call_context (call_frame : 'Optional[FrameType]' , trace : bool ) -> 'List[DebugFrame]' :
266+ call_context : 'List[DebugFrame]' = []
267+
268+ while call_frame :
269+ frame_info = DebugFrame .from_call_frame (call_frame )
270+ call_context .insert (0 , frame_info )
271+ call_frame = call_frame .f_back
272+
273+ if not trace :
274+ break
275+
276+ return call_context
277+
278+
228279debug = Debug ()
0 commit comments