1
1
"""Main module."""
2
2
from IPython import get_ipython
3
- from IPython .core .magic import ( Magics , magics_class , cell_magic , line_magic )
3
+ from IPython .core .magic import Magics , magics_class , cell_magic , line_magic
4
4
from IPython .core .extensions import ExtensionManager
5
- import ast
5
+ from IPython import get_ipython
6
6
7
+ import ast
7
8
8
-
9
9
10
10
# keep it here for reactive cells later??
11
11
@magics_class
12
12
class BifrostTracing (Magics ):
13
-
14
13
@line_magic
15
14
def line_tracing (self , line ):
16
15
return line
@@ -25,67 +24,221 @@ def __init__(self, ip):
25
24
self .shell = ip
26
25
self .last_x = None
27
26
self .bifrost_table = {}
27
+ self .plot_output = ""
28
+ self .bifrost_input = ""
29
+ self .visitor = None
28
30
29
31
def post_run_cell (self , result ):
30
- ast_tree = ast .parse (result .info .raw_cell )
31
- assignVisitor = AssignVisitor ()
32
- assignVisitor .visit (ast_tree )
33
-
34
- for new_df in assignVisitor .new_dfs :
35
- columns = get_ipython ().run_cell (new_df + '.columns' ).result
36
- print (columns )
37
- if new_df in self .bifrost_table :
38
- columns_set = set (columns )
39
- table_set = set (self .bifrost_table [new_df ].keys ())
40
- for new_col in (columns_set - table_set ):
41
- self .bifrost_table [new_df ][new_col ] = 0
42
- else :
43
- self .bifrost_table [new_df ] = {col : 0 for col in columns }
44
-
32
+ if not result .error_in_exec :
33
+ ast_tree = ast .parse (result .info .raw_cell )
34
+
35
+ callVisitor = CallVisitor ()
36
+ callVisitor .visit (ast_tree )
37
+ print (f"args={ callVisitor .args } " )
38
+
39
+ for arg in callVisitor .args :
40
+ key , value = arg .split ("." )
41
+ if key in self .bifrost_table and value in self .bifrost_table [key ]:
42
+ self .bifrost_table [key ][value ] = self .bifrost_table [key ][value ] + 1
43
+ elif key in self .bifrost_table and value not in self .bifrost_table [key ]:
44
+ self .bifrost_table [key ][value ] = 1
45
+ else :
46
+ self .bifrost_table [key ] = {value : 1 }
47
+ # assignVisitor = AssignVisitor()
48
+ # assignVisitor.visit(ast_tree)
49
+
50
+ # for new_df in assignVisitor.new_dfs:
51
+ # columns = get_ipython().run_cell(new_df + '.columns').result
52
+ # print(columns)
53
+ # if new_df in self.bifrost_table:
54
+ # columns_set = set(columns)
55
+ # table_set = set(self.bifrost_table[new_df].keys())
56
+ # for new_col in (columns_set - table_set):
57
+ # self.bifrost_table[new_df][new_col] = 0
58
+ # else:
59
+ # self.bifrost_table[new_df] = {col: 0 for col in columns}
60
+
61
+
62
+ class AttributeVisitor (ast .NodeVisitor ):
63
+ def __init__ (self ):
64
+ self .attributes = []
45
65
46
- class AttributeVisitor (ast .NodeVisitor ):
47
66
def visit_Attribute (self , node ):
48
- self .attributes = []
49
- self .attributes .append (node .value .id + "." + node .attr )
67
+ if isinstance (node , ast .Attribute ):
68
+ self .visit (node .value )
69
+ if isinstance (node .value , ast .Name ):
70
+ self .attributes .append (node .attr )
71
+ elif isinstance (node .value , ast .Call ):
72
+ callVisitor = CallVisitor ()
73
+ callVisitor .visit (node .value )
50
74
51
75
52
76
class NameVisitor (ast .NodeVisitor ):
53
- def visit_Name (self , node ):
77
+ def __init__ (self ):
54
78
self .names = []
79
+
80
+ def visit_Name (self , node ):
55
81
self .names .append (node .id )
56
82
57
83
58
84
class AssignVisitor (ast .NodeVisitor ):
59
85
def __init__ (self ):
60
86
self .new_dfs = []
87
+ self .output_var = None
88
+ self .bifrost_input = None
61
89
62
90
def visit_Module (self , node ):
63
91
self .generic_visit (node )
64
-
92
+
65
93
def visit_Assign (self , node ):
66
94
nameVisitor = NameVisitor ()
67
95
for target in node .targets :
68
96
nameVisitor .visit (target )
69
97
names = nameVisitor .names
70
98
attributeVisitor = AttributeVisitor ()
71
- attributeVisitor .visit (node .value )
99
+ attributeVisitor .visit (node .value )
72
100
attributes = attributeVisitor .attributes
73
101
df_mask = [call == "pd.DataFrame" for call in attributes ]
74
- self .new_dfs .extend ([name for name , is_df in zip (names , df_mask ) if is_df ])
75
-
102
+ plot_mask = "bifrost.plot" in attributes
103
+ if "DataFrame" in attributes :
104
+ self .new_dfs .extend (names )
105
+ if "bifrost.plot" in attributes :
106
+ self .output_var = names [- 1 ] if len (names ) else None
107
+ nameVisitor = NameVisitor ()
108
+ nameVisitor .visit (node )
109
+ self .bifrost_input = nameVisitor .names [- 1 ]
110
+
111
+
112
+ class SubscriptVisitor (ast .NodeVisitor ):
113
+ def visit_Subscript (self , node : ast .Subscript ):
114
+ self .subscripts = []
115
+ if isinstance (node .slice , ast .Tuple ):
116
+ columns = [element .value for element in node .slice .elts ]
117
+ for column in columns :
118
+ self .subscripts .append [[node .value .id , column ]]
119
+ elif isinstance (node .slice , ast .Compare ):
120
+ left = node .slice .left
121
+ if isinstance (left , ast .Subscript ):
122
+ self .visit_Subscript (left )
123
+ else :
124
+ self .subscripts .append ([left .value .id , left .attr ])
125
+ else :
126
+ self .subscripts .append ([node .value .id , node .slice .value ])
127
+
128
+
129
+ class CallVisitor (ast .NodeVisitor ):
130
+ def __init__ (self ):
131
+ self .args = []
76
132
133
+ def visit_Module (self , node ):
134
+ self .generic_visit (node )
77
135
136
+ def visit_Assign (self , node ):
137
+ if isinstance (node .targets [0 ], ast .Subscript ):
138
+ self .get_args (node .targets [0 ])
139
+
140
+ def visit_Expr (self , node ):
141
+ if isinstance (node .value , ast .Call ):
142
+ self .visit_Call (node .value )
143
+ elif isinstance (node .value , ast .Subscript ):
144
+ self .get_args (node .value )
145
+ elif isinstance (node .value , ast .Attribute ):
146
+ if isinstance (node .value .value , ast .Name ):
147
+ self .get_args (node .value .attr , node .value .value .id )
148
+ elif isinstance (node .value .value , ast .Subscript ):
149
+ self .get_args (node .value .value )
150
+
151
+ def visit_Call (self , node ):
152
+ attributeVisitor = AttributeVisitor ()
153
+ if not isinstance (node .func , ast .Attribute ):
154
+ return
155
+ if isinstance (node .func .value , ast .Subscript ):
156
+ self .get_args (node .func .value )
157
+ elif isinstance (node .func .value , ast .Name ):
158
+ func = node .func .value .id
159
+ # NP case
160
+ if func in ["np" , "numpy" ]:
161
+ attributeVisitor .visit (node .func )
162
+ if attributeVisitor .attributes [0 ] in ["mean" , "std" , "sum" ]:
163
+ args = node .args
164
+ if len (args ) != 0 :
165
+ self .get_args (args [0 ])
166
+ else :
167
+ # check keywords
168
+ keywords = node .keywords
169
+ for keyword in keywords :
170
+ if keyword .arg == "a" :
171
+ self .get_args (keyword .value )
172
+ # DF/PD case
173
+ elif func in ["df" , "pd" ]:
174
+ attributeVisitor .visit (node .func )
175
+ attribute = attributeVisitor .attributes [0 ]
176
+ if attribute in [
177
+ "groupby" ,
178
+ "loc" ,
179
+ "iloc" ,
180
+ ]:
181
+ args = node .args
182
+ if len (args ) != 0 :
183
+ if isinstance (node .func .value , ast .Name ):
184
+ self .get_args (args [0 ], node .func .value .id )
185
+ else :
186
+ self .get_args (args [0 ])
187
+ else :
188
+ # check keywords
189
+ keywords = node .keywords
190
+ for keyword in keywords :
191
+ if attribute == "groupby" and keyword .arg == "by" :
192
+ self .get_args (keyword .value )
193
+
194
+ """value: either ast.Subscript or ast.Attribute"""
195
+
196
+ def get_args (self , value , dataframe = None ):
197
+ # case arg is df['one']
198
+ if isinstance (value , ast .Subscript ):
199
+ subscriptVisitor = SubscriptVisitor ()
200
+ subscriptVisitor .visit (value )
201
+ self .args .append (
202
+ f"{ subscriptVisitor .subscripts [0 ][0 ]} .{ subscriptVisitor .subscripts [0 ][1 ]} "
203
+ )
204
+ # case arg is df.one
205
+ elif isinstance (value , ast .Attribute ):
206
+ attributeVisitor = AttributeVisitor ()
207
+ nameVisitor = NameVisitor ()
208
+ attributeVisitor .visit (value )
209
+ nameVisitor .visit (value .value )
210
+ df = nameVisitor .names [0 ]
211
+ column = attributeVisitor .attributes [0 ]
212
+ self .args .append (f"{ df } .{ column } " )
213
+ elif isinstance (value , ast .List ):
214
+ columns = [element .value for element in value .elts ]
215
+ if dataframe :
216
+ for column in columns :
217
+ self .args .append (f"{ dataframe } .{ column } " )
218
+ elif isinstance (value , ast .Constant ):
219
+ if dataframe :
220
+ self .args .append (f"{ dataframe } .{ value .value } " )
221
+
222
+
223
+ # some_var = ...bifrost.plot()
78
224
def isnotebook ():
79
225
try :
80
226
shell = get_ipython ().__class__ .__name__
81
- if shell == ' ZMQInteractiveShell' :
82
- return True # Jupyter notebook or qtconsole
83
- elif shell == ' TerminalInteractiveShell' :
227
+ if shell == " ZMQInteractiveShell" :
228
+ return True # Jupyter notebook or qtconsole
229
+ elif shell == " TerminalInteractiveShell" :
84
230
return False # Terminal running IPython
85
231
else :
86
232
return False # Other type (?)
87
233
except NameError :
88
- return False # Probably standard Python interpreter
234
+ return False # Probably standard Python interpreter
235
+
89
236
237
+ def load_ipython_extension (ipython ):
238
+ ipython .register_magics (BifrostTracing )
239
+ vw = BifrostWatcher (ipython )
240
+ ipython .events .register ("post_run_cell" , vw .post_run_cell )
241
+ return vw
90
242
91
243
244
+ Watcher = load_ipython_extension (get_ipython ())
0 commit comments