22
22
StrType = str
23
23
24
24
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
+
25
66
class DebugArgument :
26
67
__slots__ = 'value' , 'name' , 'extra'
27
68
@@ -66,43 +107,37 @@ class DebugOutput:
66
107
"""
67
108
68
109
arg_class = DebugArgument
69
- __slots__ = 'filename' , 'lineno' , 'frame ' , 'arguments' , 'warning'
110
+ __slots__ = 'call_context ' , 'arguments' , 'warning'
70
111
71
112
def __init__ (
72
113
self ,
73
114
* ,
74
- filename : str ,
75
- lineno : int ,
76
- frame : str ,
115
+ call_context : 'List[DebugFrame]' ,
77
116
arguments : 'List[DebugArgument]' ,
78
117
warning : 'Union[None, str, bool]' = None ,
79
118
) -> None :
80
- self .filename = filename
81
- self .lineno = lineno
82
- self .frame = frame
119
+ self .call_context = call_context
83
120
self .arguments = arguments
84
121
self .warning = warning
85
122
86
123
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 :
93
128
prefix += sformat (f' ({ self .warning } )' , sformat .dim )
94
- else :
95
- prefix = f'{ self .filename } :{ self .lineno } { self .frame } '
96
- if self .warning :
129
+ else :
97
130
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 )
99
133
100
134
def __str__ (self ) -> StrType :
101
135
return self .str ()
102
136
103
137
def __repr__ (self ) -> StrType :
138
+ context = self .call_context [- 1 ]
104
139
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 } >'
106
141
107
142
108
143
class Debug :
@@ -118,9 +153,10 @@ def __call__(
118
153
file_ : 'Any' = None ,
119
154
flush_ : bool = True ,
120
155
frame_depth_ : int = 2 ,
156
+ trace_ : bool = False ,
121
157
** kwargs : 'Any' ,
122
158
) -> 'Any' :
123
- d_out = self ._process (args , kwargs , frame_depth_ )
159
+ d_out = self ._process (args , kwargs , frame_depth_ , trace_ )
124
160
s = d_out .str (use_highlight (self ._highlight , file_ ))
125
161
print (s , file = file_ , flush = flush_ )
126
162
if kwargs :
@@ -130,8 +166,25 @@ def __call__(
130
166
else :
131
167
return args
132
168
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_ )
135
188
136
189
def breakpoint (self ) -> None :
137
190
import pdb
@@ -141,38 +194,24 @@ def breakpoint(self) -> None:
141
194
def timer (self , name : 'Optional[str]' = None , * , verbose : bool = True , file : 'Any' = None , dp : int = 3 ) -> Timer :
142
195
return Timer (name = name , verbose = verbose , file = file , dp = dp )
143
196
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 :
145
198
"""
146
199
BEWARE: this must be called from a function exactly `frame_depth` levels below the top of the stack.
147
200
"""
148
201
# HELP: any errors other than ValueError from _getframe? If so please submit an issue
149
202
try :
150
203
call_frame : 'FrameType' = sys ._getframe (frame_depth )
151
204
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"
153
207
return self .output_class (
154
- filename = '<unknown>' ,
155
- lineno = 0 ,
156
- frame = '' ,
208
+ call_context = [DebugFrame (function = '' , path = '<unknown>' , lineno = 0 )],
157
209
arguments = list (self ._args_inspection_failed (args , kwargs )),
158
210
warning = self ._show_warnings and 'error parsing code, call stack too shallow' ,
159
211
)
160
212
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 )
174
214
175
- lineno = call_frame .f_lineno
176
215
warning = None
177
216
178
217
import executing
@@ -183,17 +222,15 @@ def _process(self, args: 'Any', kwargs: 'Any', frame_depth: int) -> DebugOutput:
183
222
arguments = list (self ._args_inspection_failed (args , kwargs ))
184
223
else :
185
224
ex = source .executing (call_frame )
186
- function = ex .code_qualname ()
225
+ call_context [ - 1 ]. function = ex .code_qualname ()
187
226
if not ex .node :
188
227
warning = 'executing failed to find the calling node'
189
228
arguments = list (self ._args_inspection_failed (args , kwargs ))
190
229
else :
191
230
arguments = list (self ._process_args (ex , args , kwargs ))
192
231
193
232
return self .output_class (
194
- filename = str (path ),
195
- lineno = lineno ,
196
- frame = function ,
233
+ call_context = call_context ,
197
234
arguments = arguments ,
198
235
warning = self ._show_warnings and warning ,
199
236
)
@@ -225,4 +262,18 @@ def _process_args(self, ex: 'Any', args: 'Any', kwargs: 'Any') -> 'Generator[Deb
225
262
yield self .output_class .arg_class (value , name = name , variable = kw_arg_names .get (name ))
226
263
227
264
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
+
228
279
debug = Debug ()
0 commit comments