forked from pelodelfuego/jupyjet
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jupyjet.py
209 lines (142 loc) · 5.17 KB
/
jupyjet.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json, requests
import os, re
import ast
from collections import OrderedDict
from functools import reduce
import psutil, codegen
import IPython
from IPython.core.magic import Magics, magics_class, line_magic
from IPython.core.display import display, Javascript
from IPython.lib import kernel as ip_ker
_jet_beg = '\n# --- JET BEG --- #\n\n'
_jet_end = '\n# --- JET END --- #\n\n'
_jet_split_re = re.compile(r'\n# --- JET (BEG|END) --- #\n\n')
# NOTEBOOK PATH
def _find_file_path():
"""
Find the local path to the current notebook.
"""
# fetch into the socket file to find the kernel id
connection_file = os.path.basename(ip_ker.get_connection_file())
kernel_id = connection_file.split('-', 1)[1].split('.')[0]
# from the kernel id, retrieve the PID of the kernel
app_dict = IPython.Application.instance().session.__dict__
pid = app_dict['_trait_values']['pid']
# from the pid, retrive the running port on the local machine
address = [a for a in psutil.Process(pid).parent().connections() if a.status == psutil.CONN_LISTEN].pop().laddr
address = ':'.join([address[0], str(address[1])])
# build the url of the client
url = 'http://' + address + '/api/sessions'
url = url.replace('http://::1', 'http://localhost')
# fetch the current session of the client
sessions = requests.get(url).json()
ntb_name_list = [sess['notebook']['path'] for sess in sessions if sess['kernel']['id'] == kernel_id]
assert len(ntb_name_list) == 1
ntb_name = '.'.join(ntb_name_list[0].split('.')[:-1]).split('/')[-1]
return {'top_dir': os.getcwd(),'file_name': ntb_name}
def _build_py_fp():
"""
Construct the path to the generated python file.
"""
mp = _find_file_path()
return mp['top_dir'] + '/' + mp['file_name'] + '.py'
def _build_ntb_fp():
"""
Construct the path to the existing notebook file.
"""
mp = _find_file_path()
return mp['top_dir'] + '/' + mp['file_name'] + '.ipynb'
# JET CODE and JET FILE
def _load_jet_code():
"""
Load the python file into jet_code format.
jet_code is a dict:
beg => content of begining (str)
body => declaration to save (OrderedDict)
symbol name => declaration
symbol name => declaration
beg => content of end (str)
"""
# the ast should be empty if the file does not exist
py_fp = _build_py_fp()
if not os.path.isfile(py_fp):
return {'beg': '',
'body': OrderedDict(),
'end': ''}
# if it does, the ast is loaded
with open(py_fp) as py_f:
file_code = _jet_split_re.split(py_f.read())[::2]
assert len(file_code) == 3, 'the jet file is not in the good format'
body_ast = ast.parse(file_code[1]).body
body_symb = OrderedDict()
for node in body_ast:
if hasattr(node, 'name'):
body_symb[node.name] = node
return {'beg': file_code[0],
'body': body_symb,
'end': file_code[2]}
def _save_jet_file(jet_code):
"""
Save the jet_code into a the jet file.
"""
# generate the code for the symbols
body_str = '\n'.join([codegen.to_source(symb)+'\n\n' for symb in jet_code['body'].values()])
# concat eveything
jet_code_str = jet_code['beg'] + \
_jet_beg + \
body_str + \
_jet_end + \
jet_code['end']
# write into the file
py_fp = _build_py_fp()
with open(py_fp, 'w') as py_file:
py_file.write(jet_code_str)
# CELL and SYMBOLS
def _find_last_decl(In, symb_name):
"""
Iter through execution flow backward to find the last existing delaration.
"""
for cell in reversed(In):
# select only function and classes
decl_ast = [decl for decl in ast.parse(cell).body if hasattr(decl, 'name')]
# retrieve the last
for decl in reversed(decl_ast):
if decl.name == symb_name:
return decl
return None
def _fetch_current_cell(In):
body = In[-1]
if len(body.split('\n')) == 1:
return ''
else:
# remove the last line
return body[:body.rfind('\n')]
# POINT OF ENTRY
@magics_class
class JetMagics(Magics):
@line_magic
def jet_beg(self, arg_line):
In = self.shell.user_ns['In']
jet_code = _load_jet_code()
jet_code['beg'] = _fetch_current_cell(In)
_save_jet_file(jet_code)
@line_magic
def jet_end(self, arg_line):
In = self.shell.user_ns['In']
jet_code = _load_jet_code()
jet_code['end'] = _fetch_current_cell(In)
_save_jet_file(jet_code)
@line_magic
def jet(self, arg_line):
In = self.shell.user_ns['In']
update_list = [name for name in arg_line.split()]
jet_code = _load_jet_code()
for name in update_list:
decl = _find_last_decl(In, name)
assert decl, 'the symbol "{}" is not defined'.format(name)
jet_code['body'][name] = decl
_save_jet_file(jet_code)
def load_ipython_extension(ip):
ip.register_magics(JetMagics)