2121# - https://github.com/gak/pycallgraph (display call-graph with graphviz after python execution)
2222
2323import argparse
24- import bdb
2524from io import StringIO
2625import sys
2726import os
3433# copy-paste For interactive ipython sessions
3534# import IPython; sys.stdout = sys.__stdout__; IPython.embed(); sys.exit()
3635
36+
3737def debug_print (* args , ** kwargs ):
3838 # print(*args, **kwargs, file=sys.__stderr__)
3939 pass
4040
41+
4142_canonic_filename_cache = dict ()
43+
44+
4245def canonic_filename (filename ):
4346 """Return canonical form of filename. (same as Bdb.canonic)
4447
@@ -58,9 +61,10 @@ def canonic_filename(filename):
5861
5962
6063@dataclasses .dataclass (frozen = True , eq = True , order = True )
61- class Call () :
64+ class Call :
6265 """A call
6366 """
67+
6468 filename : str
6569 linenum : int
6670 inst_index : int
@@ -74,9 +78,9 @@ def from_frame(cls, frame):
7478 debug_print (b .dis ())
7579
7680 return cls (
77- filename = canonic_filename (code .co_filename ),
78- linenum = frame .f_lineno ,
79- inst_index = frame .f_lasti ,
81+ filename = canonic_filename (code .co_filename ),
82+ linenum = frame .f_lineno ,
83+ inst_index = frame .f_lasti ,
8084 )
8185
8286
@@ -85,15 +89,23 @@ def better_compare_for_dataclass(cls):
8589 objects of the same class. This decorator extends the functionality to compare class
8690 name if used against other objects.
8791 """
88- for op in ['__lt__' , '__le__' , '__gt__' , '__ge__' ,]:
92+ for op in [
93+ "__lt__" ,
94+ "__le__" ,
95+ "__gt__" ,
96+ "__ge__" ,
97+ ]:
8998 old = getattr (cls , op )
99+
90100 def new (self , other ):
91101 if type (self ) == type (other ):
92102 return old (self , other )
93103 return getattr (str , op )(self .__class__ .__name__ , other .__class__ .__name__ )
104+
94105 setattr (cls , op , new )
95106 return cls
96107
108+
97109@dataclasses .dataclass (frozen = True , eq = True , order = True )
98110class Callee :
99111 pass
@@ -115,12 +127,12 @@ class ExternalCallee(Callee):
115127 @classmethod
116128 def from_arg (cls , func ):
117129 # if func.__name__ == "append":
118- # import IPython; sys.stdout = sys.__stdout__; IPython.embed(); sys.exit()
130+ # import IPython; sys.stdout = sys.__stdout__; IPython.embed(); sys.exit()
119131
120132 return cls (
121133 module = func .__module__ ,
122134 qualname = func .__qualname__ ,
123- is_builtin = type (func ) == BUILTIN_FUNCTION_OR_METHOD
135+ is_builtin = type (func ) == BUILTIN_FUNCTION_OR_METHOD ,
124136 )
125137
126138
@@ -132,6 +144,7 @@ class PythonCallee(Callee):
132144 should (hopefully) be uniquely identified by its name and location (filename+line
133145 number)
134146 """
147+
135148 filename : str
136149 linenum : int
137150 funcname : str
@@ -140,9 +153,9 @@ class PythonCallee(Callee):
140153 def from_frame (cls , frame ):
141154 code = frame .f_code
142155 return cls (
143- filename = canonic_filename (code .co_filename ),
144- linenum = frame .f_lineno ,
145- funcname = code .co_name ,
156+ filename = canonic_filename (code .co_filename ),
157+ linenum = frame .f_lineno ,
158+ funcname = code .co_name ,
146159 )
147160
148161
@@ -186,7 +199,8 @@ def run(self, code, globals, locals):
186199 sys .setprofile (None )
187200
188201 def profilefunc (self , frame , event , arg ):
189- # ignore everything until the first call, since that is `exec` from the `run` method above
202+ # ignore everything until the first call, since that is `exec` from the `run`
203+ # method above
190204 if not self .exec_call_seen :
191205 if event == "call" :
192206 self .exec_call_seen = True
@@ -211,13 +225,13 @@ def profilefunc(self, frame, event, arg):
211225 callee = PythonCallee .from_frame (frame )
212226
213227 if event == "c_call" :
214- # in c_call, the `frame` argument is frame where the call happens, and the `arg` argument
215- # is the C function object.
228+ # in c_call, the `frame` argument is frame where the call happens, and the
229+ # `arg` argument is the C function object.
216230 call = Call .from_frame (frame )
217231 callee = ExternalCallee .from_arg (arg )
218232
219- debug_print (f' { call } --> { callee } ' )
220- debug_print (' \n ' * 5 )
233+ debug_print (f" { call } --> { callee } " )
234+ debug_print (" \n " * 5 )
221235 self .recorded_calls .add ((call , callee ))
222236
223237
@@ -227,7 +241,6 @@ def profilefunc(self, frame, event, arg):
227241
228242
229243class Exporter :
230-
231244 @staticmethod
232245 def export (recorded_calls , outfile_path ):
233246 raise NotImplementedError ()
@@ -240,15 +253,14 @@ def dataclass_to_dict(obj):
240253
241254
242255class CSVExporter (Exporter ):
243-
244256 @staticmethod
245257 def export (recorded_calls , outfile_path ):
246- with open (outfile_path , 'w' , newline = '' ) as csv_file :
258+ with open (outfile_path , "w" , newline = "" ) as csv_file :
247259 writer = None
248260 for (call , callee ) in sorted (recorded_calls ):
249261 data = {
250262 ** Exporter .dataclass_to_dict (call ),
251- ** Exporter .dataclass_to_dict (callee )
263+ ** Exporter .dataclass_to_dict (callee ),
252264 }
253265
254266 if writer is None :
@@ -257,32 +269,30 @@ def export(recorded_calls, outfile_path):
257269
258270 writer .writerow (data )
259271
260-
261- print (f'output written to { outfile_path } ' )
272+ print (f"output written to { outfile_path } " )
262273
263274 # embed(); sys.exit()
264275
265276
266277class XMLExporter (Exporter ):
267-
268278 @staticmethod
269279 def export (recorded_calls , outfile_path ):
270280
271- root = etree .Element (' root' )
281+ root = etree .Element (" root" )
272282
273283 for (call , callee ) in sorted (recorded_calls ):
274284 data = {
275285 ** Exporter .dataclass_to_dict (call ),
276- ** Exporter .dataclass_to_dict (callee )
286+ ** Exporter .dataclass_to_dict (callee ),
277287 }
278288
279- rc = etree .SubElement (root , ' recorded_call' )
289+ rc = etree .SubElement (root , " recorded_call" )
280290 for k , v in data .items ():
281291 # xml library only supports serializing attributes that have string values
282292 rc .set (k , str (v ))
283293
284294 tree = etree .ElementTree (root )
285- tree .write (outfile_path , encoding = ' utf-8' , pretty_print = True )
295+ tree .write (outfile_path , encoding = " utf-8" , pretty_print = True )
286296
287297
288298################################################################################
@@ -293,13 +303,13 @@ def export(recorded_calls, outfile_path):
293303if __name__ == "__main__" :
294304 parser = argparse .ArgumentParser ()
295305
306+ parser .add_argument ("--csv" )
307+ parser .add_argument ("--xml" )
296308
297- parser .add_argument ('--csv' )
298- parser .add_argument ('--xml' )
299-
300- parser .add_argument ('progname' , help = 'file to run as main program' )
301- parser .add_argument ('arguments' , nargs = argparse .REMAINDER ,
302- help = 'arguments to the program' )
309+ parser .add_argument ("progname" , help = "file to run as main program" )
310+ parser .add_argument (
311+ "arguments" , nargs = argparse .REMAINDER , help = "arguments to the program"
312+ )
303313
304314 opts = parser .parse_args ()
305315
@@ -309,14 +319,14 @@ def export(recorded_calls, outfile_path):
309319 sys .path [0 ] = os .path .dirname (opts .progname )
310320
311321 with open (opts .progname ) as fp :
312- code = compile (fp .read (), opts .progname , ' exec' )
322+ code = compile (fp .read (), opts .progname , " exec" )
313323
314324 # try to emulate __main__ namespace as much as possible
315325 globs = {
316- ' __file__' : opts .progname ,
317- ' __name__' : ' __main__' ,
318- ' __package__' : None ,
319- ' __cached__' : None ,
326+ " __file__" : opts .progname ,
327+ " __name__" : " __main__" ,
328+ " __package__" : None ,
329+ " __cached__" : None ,
320330 }
321331
322332 real_stdout = sys .stdout
@@ -334,7 +344,7 @@ def export(recorded_calls, outfile_path):
334344 XMLExporter .export (cgt .recorded_calls , opts .xml )
335345 else :
336346 for (call , callee ) in sorted (cgt .recorded_calls ):
337- print (f' { call } --> { callee } ' )
347+ print (f" { call } --> { callee } " )
338348
339- print (' --- captured stdout ---' )
340- print (captured_stdout .getvalue (), end = '' )
349+ print (" --- captured stdout ---" )
350+ print (captured_stdout .getvalue (), end = "" )
0 commit comments