more-compute 0.2.6__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- frontend/app/globals.css +38 -133
- frontend/app/layout.tsx +54 -5
- frontend/components/Notebook.tsx +9 -1
- frontend/components/cell/CellButton.tsx +2 -2
- frontend/components/cell/MonacoCell.tsx +1 -15
- frontend/components/output/CellOutput.tsx +77 -17
- frontend/components/output/ErrorDisplay.tsx +3 -28
- frontend/components/popups/FilterPopup.tsx +24 -24
- frontend/components/popups/MetricsPopup.tsx +42 -7
- frontend/components/popups/PackagesPopup.tsx +2 -1
- frontend/lib/api.ts +6 -2
- frontend/lib/settings.ts +7 -0
- frontend/lib/websocket-native.ts +3 -0
- frontend/styling_README.md +15 -2
- kernel_run.py +26 -13
- {more_compute-0.2.6.dist-info → more_compute-0.3.1.dist-info}/METADATA +1 -1
- {more_compute-0.2.6.dist-info → more_compute-0.3.1.dist-info}/RECORD +29 -27
- morecompute/__version__.py +1 -1
- morecompute/execution/executor.py +12 -5
- morecompute/execution/worker.py +93 -1
- morecompute/server.py +4 -0
- morecompute/utils/cell_magics.py +713 -0
- morecompute/utils/line_magics.py +949 -0
- morecompute/utils/shell_utils.py +68 -0
- morecompute/utils/special_commands.py +106 -173
- frontend/components/Cell.tsx +0 -383
- {more_compute-0.2.6.dist-info → more_compute-0.3.1.dist-info}/WHEEL +0 -0
- {more_compute-0.2.6.dist-info → more_compute-0.3.1.dist-info}/entry_points.txt +0 -0
- {more_compute-0.2.6.dist-info → more_compute-0.3.1.dist-info}/licenses/LICENSE +0 -0
- {more_compute-0.2.6.dist-info → more_compute-0.3.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import shlex
|
|
5
|
+
import subprocess
|
|
6
|
+
import importlib
|
|
7
|
+
from typing import Dict, Any, Optional, List
|
|
8
|
+
from fastapi import WebSocket
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class LineMagicHandlers:
|
|
12
|
+
"""Handlers for IPython line magic commands (%magic)"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, globals_dict: dict):
|
|
15
|
+
self.globals_dict = globals_dict
|
|
16
|
+
self.directory_stack = []
|
|
17
|
+
self.directory_history = []
|
|
18
|
+
self.loaded_extensions = {} # Track loaded extensions
|
|
19
|
+
self.matplotlib_backend = None # Track matplotlib backend
|
|
20
|
+
|
|
21
|
+
async def handle_pwd(self, args: list, result: Dict[str, Any],
|
|
22
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
23
|
+
"""Handle %pwd - print working directory"""
|
|
24
|
+
try:
|
|
25
|
+
pwd = os.getcwd()
|
|
26
|
+
output_data = {
|
|
27
|
+
"output_type": "execute_result",
|
|
28
|
+
"execution_count": None,
|
|
29
|
+
"data": {
|
|
30
|
+
"text/plain": f"'{pwd}'"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
result["outputs"].append(output_data)
|
|
34
|
+
|
|
35
|
+
if websocket:
|
|
36
|
+
await websocket.send_json({
|
|
37
|
+
"type": "execute_result",
|
|
38
|
+
"data": {"data": output_data["data"]}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
except Exception as e:
|
|
42
|
+
result["status"] = "error"
|
|
43
|
+
result["error"] = {
|
|
44
|
+
"ename": type(e).__name__,
|
|
45
|
+
"evalue": str(e),
|
|
46
|
+
"traceback": [f"PWD magic error: {str(e)}"]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
async def handle_cd(self, args: list, result: Dict[str, Any],
|
|
52
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
53
|
+
"""Handle %cd - change directory"""
|
|
54
|
+
try:
|
|
55
|
+
if not args:
|
|
56
|
+
# No args - go to home directory
|
|
57
|
+
new_dir = os.path.expanduser('~')
|
|
58
|
+
else:
|
|
59
|
+
new_dir = os.path.expanduser(args[0])
|
|
60
|
+
|
|
61
|
+
old_dir = os.getcwd()
|
|
62
|
+
os.chdir(new_dir)
|
|
63
|
+
self.directory_history.append(new_dir)
|
|
64
|
+
|
|
65
|
+
output_text = os.getcwd() + "\n"
|
|
66
|
+
result["outputs"].append({
|
|
67
|
+
"output_type": "stream",
|
|
68
|
+
"name": "stdout",
|
|
69
|
+
"text": output_text
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
if websocket:
|
|
73
|
+
await websocket.send_json({
|
|
74
|
+
"type": "stream_output",
|
|
75
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
except Exception as e:
|
|
79
|
+
result["status"] = "error"
|
|
80
|
+
result["error"] = {
|
|
81
|
+
"ename": type(e).__name__,
|
|
82
|
+
"evalue": str(e),
|
|
83
|
+
"traceback": [f"CD magic error: {str(e)}"]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result
|
|
87
|
+
|
|
88
|
+
async def handle_ls(self, args: list, result: Dict[str, Any],
|
|
89
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
90
|
+
"""Handle %ls - list directory contents"""
|
|
91
|
+
try:
|
|
92
|
+
path = args[0] if args else '.'
|
|
93
|
+
items = os.listdir(path)
|
|
94
|
+
items.sort()
|
|
95
|
+
|
|
96
|
+
output_text = '\n'.join(items) + "\n"
|
|
97
|
+
result["outputs"].append({
|
|
98
|
+
"output_type": "stream",
|
|
99
|
+
"name": "stdout",
|
|
100
|
+
"text": output_text
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
if websocket:
|
|
104
|
+
await websocket.send_json({
|
|
105
|
+
"type": "stream_output",
|
|
106
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
result["status"] = "error"
|
|
111
|
+
result["error"] = {
|
|
112
|
+
"ename": type(e).__name__,
|
|
113
|
+
"evalue": str(e),
|
|
114
|
+
"traceback": [f"LS magic error: {str(e)}"]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return result
|
|
118
|
+
|
|
119
|
+
async def handle_env(self, args: list, result: Dict[str, Any],
|
|
120
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
121
|
+
"""
|
|
122
|
+
Handle %env - get/set environment variables.
|
|
123
|
+
Usage:
|
|
124
|
+
%env - list all env vars
|
|
125
|
+
%env VAR - get value of VAR
|
|
126
|
+
%env VAR=value - set VAR to value
|
|
127
|
+
%env VAR=$OTHER - set VAR to value of OTHER (with expansion)
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
if not args:
|
|
131
|
+
# List all environment variables
|
|
132
|
+
env_text = '\n'.join([f"{k}={v}" for k, v in sorted(os.environ.items())])
|
|
133
|
+
output_text = env_text + "\n"
|
|
134
|
+
elif '=' in args[0]:
|
|
135
|
+
# Set environment variable
|
|
136
|
+
var_assignment = ' '.join(args)
|
|
137
|
+
var_name, var_value = var_assignment.split('=', 1)
|
|
138
|
+
|
|
139
|
+
# Handle $VAR expansion
|
|
140
|
+
if var_value.startswith('$'):
|
|
141
|
+
var_value = os.environ.get(var_value[1:], '')
|
|
142
|
+
|
|
143
|
+
os.environ[var_name] = var_value
|
|
144
|
+
output_text = f"env: {var_name}={var_value}\n"
|
|
145
|
+
else:
|
|
146
|
+
# Get environment variable
|
|
147
|
+
var_name = args[0]
|
|
148
|
+
var_value = os.environ.get(var_name, '')
|
|
149
|
+
output_text = var_value + "\n" if var_value else f"env: {var_name} not set\n"
|
|
150
|
+
|
|
151
|
+
result["outputs"].append({
|
|
152
|
+
"output_type": "stream",
|
|
153
|
+
"name": "stdout",
|
|
154
|
+
"text": output_text
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if websocket:
|
|
158
|
+
await websocket.send_json({
|
|
159
|
+
"type": "stream_output",
|
|
160
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
except Exception as e:
|
|
164
|
+
result["status"] = "error"
|
|
165
|
+
result["error"] = {
|
|
166
|
+
"ename": type(e).__name__,
|
|
167
|
+
"evalue": str(e),
|
|
168
|
+
"traceback": [f"ENV magic error: {str(e)}"]
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
async def handle_who(self, args: list, result: Dict[str, Any],
|
|
174
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
175
|
+
"""
|
|
176
|
+
Handle %who - list interactive variables.
|
|
177
|
+
Usage: %who [type] - optionally filter by type (e.g., %who int, %who str)
|
|
178
|
+
"""
|
|
179
|
+
try:
|
|
180
|
+
# Get all user-defined variables (exclude built-ins and private vars)
|
|
181
|
+
user_vars = {k: v for k, v in self.globals_dict.items()
|
|
182
|
+
if not k.startswith('_') and k not in ['In', 'Out']}
|
|
183
|
+
|
|
184
|
+
# Filter by type if specified
|
|
185
|
+
if args:
|
|
186
|
+
type_name = args[0]
|
|
187
|
+
type_map = {
|
|
188
|
+
'int': int,
|
|
189
|
+
'float': float,
|
|
190
|
+
'str': str,
|
|
191
|
+
'list': list,
|
|
192
|
+
'dict': dict,
|
|
193
|
+
'tuple': tuple,
|
|
194
|
+
'set': set,
|
|
195
|
+
}
|
|
196
|
+
if type_name in type_map:
|
|
197
|
+
user_vars = {k: v for k, v in user_vars.items()
|
|
198
|
+
if isinstance(v, type_map[type_name])}
|
|
199
|
+
|
|
200
|
+
# Format output
|
|
201
|
+
var_names = sorted(user_vars.keys())
|
|
202
|
+
output_text = '\t'.join(var_names) + "\n" if var_names else "Interactive namespace is empty.\n"
|
|
203
|
+
|
|
204
|
+
result["outputs"].append({
|
|
205
|
+
"output_type": "stream",
|
|
206
|
+
"name": "stdout",
|
|
207
|
+
"text": output_text
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
if websocket:
|
|
211
|
+
await websocket.send_json({
|
|
212
|
+
"type": "stream_output",
|
|
213
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
except Exception as e:
|
|
217
|
+
result["status"] = "error"
|
|
218
|
+
result["error"] = {
|
|
219
|
+
"ename": type(e).__name__,
|
|
220
|
+
"evalue": str(e),
|
|
221
|
+
"traceback": [f"WHO magic error: {str(e)}"]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result
|
|
225
|
+
|
|
226
|
+
async def handle_whos(self, args: list, result: Dict[str, Any],
|
|
227
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
228
|
+
"""
|
|
229
|
+
Handle %whos - list variables with type and size information.
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
# Get all user-defined variables
|
|
233
|
+
user_vars = {k: v for k, v in self.globals_dict.items()
|
|
234
|
+
if not k.startswith('_') and k not in ['In', 'Out']}
|
|
235
|
+
|
|
236
|
+
if not user_vars:
|
|
237
|
+
output_text = "Interactive namespace is empty.\n"
|
|
238
|
+
else:
|
|
239
|
+
# Build table header
|
|
240
|
+
lines = []
|
|
241
|
+
lines.append("Variable Type Data/Info")
|
|
242
|
+
lines.append("-" * 50)
|
|
243
|
+
|
|
244
|
+
for name in sorted(user_vars.keys()):
|
|
245
|
+
var = user_vars[name]
|
|
246
|
+
var_type = type(var).__name__
|
|
247
|
+
|
|
248
|
+
# Get size/shape info
|
|
249
|
+
if hasattr(var, 'shape'): # NumPy arrays, pandas DataFrames
|
|
250
|
+
info = f"shape: {var.shape}"
|
|
251
|
+
elif hasattr(var, '__len__'):
|
|
252
|
+
try:
|
|
253
|
+
info = f"n={len(var)}"
|
|
254
|
+
except:
|
|
255
|
+
info = ""
|
|
256
|
+
else:
|
|
257
|
+
info = str(var)[:50] # First 50 chars
|
|
258
|
+
|
|
259
|
+
lines.append(f"{name:<10} {var_type:<11} {info}")
|
|
260
|
+
|
|
261
|
+
output_text = '\n'.join(lines) + "\n"
|
|
262
|
+
|
|
263
|
+
result["outputs"].append({
|
|
264
|
+
"output_type": "stream",
|
|
265
|
+
"name": "stdout",
|
|
266
|
+
"text": output_text
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
if websocket:
|
|
270
|
+
await websocket.send_json({
|
|
271
|
+
"type": "stream_output",
|
|
272
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
result["status"] = "error"
|
|
277
|
+
result["error"] = {
|
|
278
|
+
"ename": type(e).__name__,
|
|
279
|
+
"evalue": str(e),
|
|
280
|
+
"traceback": [f"WHOS magic error: {str(e)}"]
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
async def handle_time(self, args: list, result: Dict[str, Any],
|
|
286
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
287
|
+
"""
|
|
288
|
+
Handle %time - time single statement execution.
|
|
289
|
+
Usage: %time statement
|
|
290
|
+
"""
|
|
291
|
+
try:
|
|
292
|
+
if not args:
|
|
293
|
+
result["status"] = "error"
|
|
294
|
+
result["error"] = {
|
|
295
|
+
"ename": "UsageError",
|
|
296
|
+
"evalue": "%time requires a statement to time",
|
|
297
|
+
"traceback": ["Usage: %time statement"]
|
|
298
|
+
}
|
|
299
|
+
return result
|
|
300
|
+
|
|
301
|
+
statement = ' '.join(args)
|
|
302
|
+
|
|
303
|
+
# Time the execution
|
|
304
|
+
start_wall = time.time()
|
|
305
|
+
start_cpu = time.process_time()
|
|
306
|
+
|
|
307
|
+
# Execute the statement
|
|
308
|
+
exec(statement, self.globals_dict)
|
|
309
|
+
|
|
310
|
+
wall_time = time.time() - start_wall
|
|
311
|
+
cpu_time = time.process_time() - start_cpu
|
|
312
|
+
|
|
313
|
+
# Format output
|
|
314
|
+
output_text = f"CPU times: user {cpu_time:.2f} s, sys: 0 s, total: {cpu_time:.2f} s\n"
|
|
315
|
+
output_text += f"Wall time: {wall_time:.2f} s\n"
|
|
316
|
+
|
|
317
|
+
result["outputs"].append({
|
|
318
|
+
"output_type": "stream",
|
|
319
|
+
"name": "stdout",
|
|
320
|
+
"text": output_text
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
if websocket:
|
|
324
|
+
await websocket.send_json({
|
|
325
|
+
"type": "stream_output",
|
|
326
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
except Exception as e:
|
|
330
|
+
result["status"] = "error"
|
|
331
|
+
result["error"] = {
|
|
332
|
+
"ename": type(e).__name__,
|
|
333
|
+
"evalue": str(e),
|
|
334
|
+
"traceback": [f"TIME magic error: {str(e)}"]
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return result
|
|
338
|
+
|
|
339
|
+
async def handle_pip(self, args: list, result: Dict[str, Any],
|
|
340
|
+
special_handler, websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
341
|
+
"""
|
|
342
|
+
Handle %pip - run pip within the kernel.
|
|
343
|
+
This delegates to the shell command handler.
|
|
344
|
+
"""
|
|
345
|
+
pip_command = 'pip ' + ' '.join(args)
|
|
346
|
+
return await special_handler._execute_shell_command(
|
|
347
|
+
pip_command, result, time.time(), websocket
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
async def handle_load(self, args: list, result: Dict[str, Any],
|
|
351
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
352
|
+
"""
|
|
353
|
+
Handle %load - load code from file or URL.
|
|
354
|
+
Usage: %load filename
|
|
355
|
+
"""
|
|
356
|
+
try:
|
|
357
|
+
if not args:
|
|
358
|
+
result["status"] = "error"
|
|
359
|
+
result["error"] = {
|
|
360
|
+
"ename": "UsageError",
|
|
361
|
+
"evalue": "%load requires a filename",
|
|
362
|
+
"traceback": ["Usage: %load filename"]
|
|
363
|
+
}
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
source = args[0]
|
|
367
|
+
|
|
368
|
+
# Check if it's a URL
|
|
369
|
+
if source.startswith('http://') or source.startswith('https://'):
|
|
370
|
+
import urllib.request
|
|
371
|
+
with urllib.request.urlopen(source) as response:
|
|
372
|
+
code = response.read().decode('utf-8')
|
|
373
|
+
else:
|
|
374
|
+
# It's a file
|
|
375
|
+
with open(source, 'r') as f:
|
|
376
|
+
code = f.read()
|
|
377
|
+
|
|
378
|
+
# In IPython, %load replaces the cell content with the loaded code
|
|
379
|
+
# For now, we'll just display it
|
|
380
|
+
output_text = f"# Loaded from {source}\n{code}\n"
|
|
381
|
+
|
|
382
|
+
result["outputs"].append({
|
|
383
|
+
"output_type": "stream",
|
|
384
|
+
"name": "stdout",
|
|
385
|
+
"text": output_text
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
if websocket:
|
|
389
|
+
await websocket.send_json({
|
|
390
|
+
"type": "stream_output",
|
|
391
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
result["status"] = "error"
|
|
396
|
+
result["error"] = {
|
|
397
|
+
"ename": type(e).__name__,
|
|
398
|
+
"evalue": str(e),
|
|
399
|
+
"traceback": [f"LOAD magic error: {str(e)}"]
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return result
|
|
403
|
+
|
|
404
|
+
async def handle_reset(self, args: list, result: Dict[str, Any],
|
|
405
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
406
|
+
"""
|
|
407
|
+
Handle %reset - clear namespace.
|
|
408
|
+
Usage: %reset [-f] [-s]
|
|
409
|
+
-f: force (no confirmation)
|
|
410
|
+
-s: soft (keep internal variables)
|
|
411
|
+
"""
|
|
412
|
+
try:
|
|
413
|
+
force = '-f' in args
|
|
414
|
+
soft = '-s' in args
|
|
415
|
+
|
|
416
|
+
# For now, implement force mode only
|
|
417
|
+
if force or True: # Always reset for now
|
|
418
|
+
# Get list of user variables to delete
|
|
419
|
+
user_vars = [k for k in self.globals_dict.keys()
|
|
420
|
+
if not k.startswith('_') and k not in ['In', 'Out']]
|
|
421
|
+
|
|
422
|
+
for var in user_vars:
|
|
423
|
+
del self.globals_dict[var]
|
|
424
|
+
|
|
425
|
+
output_text = "All user variables have been reset.\n"
|
|
426
|
+
else:
|
|
427
|
+
output_text = "Reset cancelled. Use %reset -f to force reset.\n"
|
|
428
|
+
|
|
429
|
+
result["outputs"].append({
|
|
430
|
+
"output_type": "stream",
|
|
431
|
+
"name": "stdout",
|
|
432
|
+
"text": output_text
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
if websocket:
|
|
436
|
+
await websocket.send_json({
|
|
437
|
+
"type": "stream_output",
|
|
438
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
except Exception as e:
|
|
442
|
+
result["status"] = "error"
|
|
443
|
+
result["error"] = {
|
|
444
|
+
"ename": type(e).__name__,
|
|
445
|
+
"evalue": str(e),
|
|
446
|
+
"traceback": [f"RESET magic error: {str(e)}"]
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return result
|
|
450
|
+
|
|
451
|
+
async def handle_lsmagic(self, args: list, result: Dict[str, Any],
|
|
452
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
453
|
+
"""Handle %lsmagic - list available magic commands"""
|
|
454
|
+
try:
|
|
455
|
+
available_line = [
|
|
456
|
+
'cd', 'pwd', 'ls', 'env', 'who', 'whos', 'time', 'timeit', 'pip',
|
|
457
|
+
'load', 'reset', 'lsmagic', 'matplotlib', 'load_ext', 'reload_ext',
|
|
458
|
+
'unload_ext', 'run'
|
|
459
|
+
]
|
|
460
|
+
available_cell = [
|
|
461
|
+
'capture', 'time', 'timeit', 'writefile', 'bash', 'sh',
|
|
462
|
+
'html', 'markdown'
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
output_text = "Available line magics:\n"
|
|
466
|
+
output_text += "%" + " %".join(available_line) + "\n\n"
|
|
467
|
+
output_text += "Available cell magics:\n"
|
|
468
|
+
output_text += "%%" + " %%".join(available_cell) + "\n"
|
|
469
|
+
|
|
470
|
+
result["outputs"].append({
|
|
471
|
+
"output_type": "stream",
|
|
472
|
+
"name": "stdout",
|
|
473
|
+
"text": output_text
|
|
474
|
+
})
|
|
475
|
+
|
|
476
|
+
if websocket:
|
|
477
|
+
await websocket.send_json({
|
|
478
|
+
"type": "stream_output",
|
|
479
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
except Exception as e:
|
|
483
|
+
result["status"] = "error"
|
|
484
|
+
result["error"] = {
|
|
485
|
+
"ename": type(e).__name__,
|
|
486
|
+
"evalue": str(e),
|
|
487
|
+
"traceback": [f"LSMAGIC magic error: {str(e)}"]
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return result
|
|
491
|
+
|
|
492
|
+
async def handle_matplotlib(self, args: list, result: Dict[str, Any],
|
|
493
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
494
|
+
"""
|
|
495
|
+
Handle %matplotlib - setup matplotlib for interactive use.
|
|
496
|
+
Usage: %matplotlib [backend]
|
|
497
|
+
Common backends: inline, notebook, widget, qt, tk
|
|
498
|
+
"""
|
|
499
|
+
try:
|
|
500
|
+
backend = args[0] if args else 'inline'
|
|
501
|
+
|
|
502
|
+
# Import matplotlib
|
|
503
|
+
import matplotlib
|
|
504
|
+
import matplotlib.pyplot as plt
|
|
505
|
+
|
|
506
|
+
# Configure based on backend
|
|
507
|
+
if backend == 'inline':
|
|
508
|
+
# For inline backend, configure matplotlib
|
|
509
|
+
matplotlib.use('Agg') # Non-interactive backend
|
|
510
|
+
self.matplotlib_backend = 'inline'
|
|
511
|
+
|
|
512
|
+
# Configure for better inline display
|
|
513
|
+
try:
|
|
514
|
+
from IPython.display import set_matplotlib_formats
|
|
515
|
+
set_matplotlib_formats('retina')
|
|
516
|
+
except:
|
|
517
|
+
pass
|
|
518
|
+
|
|
519
|
+
elif backend == 'notebook':
|
|
520
|
+
# Interactive notebook backend
|
|
521
|
+
try:
|
|
522
|
+
import ipympl
|
|
523
|
+
matplotlib.use('module://ipympl.backend_nbagg')
|
|
524
|
+
self.matplotlib_backend = 'notebook'
|
|
525
|
+
except ImportError:
|
|
526
|
+
output_text = "Warning: ipympl not installed. Falling back to inline.\n"
|
|
527
|
+
result["outputs"].append({
|
|
528
|
+
"output_type": "stream",
|
|
529
|
+
"name": "stderr",
|
|
530
|
+
"text": output_text
|
|
531
|
+
})
|
|
532
|
+
matplotlib.use('Agg')
|
|
533
|
+
self.matplotlib_backend = 'inline'
|
|
534
|
+
|
|
535
|
+
elif backend in ['qt', 'qt5', 'qt4']:
|
|
536
|
+
matplotlib.use('Qt5Agg')
|
|
537
|
+
self.matplotlib_backend = backend
|
|
538
|
+
|
|
539
|
+
elif backend == 'tk':
|
|
540
|
+
matplotlib.use('TkAgg')
|
|
541
|
+
self.matplotlib_backend = backend
|
|
542
|
+
|
|
543
|
+
elif backend == 'widget':
|
|
544
|
+
# Jupyter widgets backend
|
|
545
|
+
try:
|
|
546
|
+
import ipympl
|
|
547
|
+
matplotlib.use('module://ipympl.backend_nbagg')
|
|
548
|
+
self.matplotlib_backend = 'widget'
|
|
549
|
+
except ImportError:
|
|
550
|
+
output_text = "Warning: ipympl not installed. Install with: pip install ipympl\n"
|
|
551
|
+
result["outputs"].append({
|
|
552
|
+
"output_type": "stream",
|
|
553
|
+
"name": "stderr",
|
|
554
|
+
"text": output_text
|
|
555
|
+
})
|
|
556
|
+
matplotlib.use('Agg')
|
|
557
|
+
self.matplotlib_backend = 'inline'
|
|
558
|
+
|
|
559
|
+
else:
|
|
560
|
+
matplotlib.use(backend)
|
|
561
|
+
self.matplotlib_backend = backend
|
|
562
|
+
|
|
563
|
+
# Store in globals for easy access
|
|
564
|
+
self.globals_dict['plt'] = plt
|
|
565
|
+
|
|
566
|
+
output_text = f"Using matplotlib backend: {self.matplotlib_backend}\n"
|
|
567
|
+
result["outputs"].append({
|
|
568
|
+
"output_type": "stream",
|
|
569
|
+
"name": "stdout",
|
|
570
|
+
"text": output_text
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
if websocket:
|
|
574
|
+
await websocket.send_json({
|
|
575
|
+
"type": "stream_output",
|
|
576
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
except Exception as e:
|
|
580
|
+
result["status"] = "error"
|
|
581
|
+
result["error"] = {
|
|
582
|
+
"ename": type(e).__name__,
|
|
583
|
+
"evalue": str(e),
|
|
584
|
+
"traceback": [f"MATPLOTLIB magic error: {str(e)}"]
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
if websocket:
|
|
588
|
+
await websocket.send_json({
|
|
589
|
+
"type": "execution_error",
|
|
590
|
+
"data": {"error": result["error"]}
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
return result
|
|
594
|
+
|
|
595
|
+
async def handle_load_ext(self, args: list, result: Dict[str, Any],
|
|
596
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
597
|
+
"""
|
|
598
|
+
Handle %load_ext - load IPython extension.
|
|
599
|
+
Usage: %load_ext extension_name
|
|
600
|
+
Common extensions: autoreload, tensorboard
|
|
601
|
+
"""
|
|
602
|
+
try:
|
|
603
|
+
if not args:
|
|
604
|
+
result["status"] = "error"
|
|
605
|
+
result["error"] = {
|
|
606
|
+
"ename": "UsageError",
|
|
607
|
+
"evalue": "%load_ext requires an extension name",
|
|
608
|
+
"traceback": ["Usage: %load_ext extension_name"]
|
|
609
|
+
}
|
|
610
|
+
return result
|
|
611
|
+
|
|
612
|
+
ext_name = args[0]
|
|
613
|
+
|
|
614
|
+
# Check if already loaded
|
|
615
|
+
if ext_name in self.loaded_extensions:
|
|
616
|
+
output_text = f"The {ext_name} extension is already loaded.\n"
|
|
617
|
+
result["outputs"].append({
|
|
618
|
+
"output_type": "stream",
|
|
619
|
+
"name": "stdout",
|
|
620
|
+
"text": output_text
|
|
621
|
+
})
|
|
622
|
+
return result
|
|
623
|
+
|
|
624
|
+
# Handle special built-in extensions
|
|
625
|
+
if ext_name == 'autoreload':
|
|
626
|
+
# Basic autoreload implementation
|
|
627
|
+
output_text = "Loaded autoreload extension (basic implementation)\n"
|
|
628
|
+
output_text += "Note: Full autoreload functionality requires IPython kernel\n"
|
|
629
|
+
self.loaded_extensions[ext_name] = {'type': 'builtin', 'config': {}}
|
|
630
|
+
|
|
631
|
+
elif ext_name == 'tensorboard':
|
|
632
|
+
# TensorBoard extension
|
|
633
|
+
try:
|
|
634
|
+
import tensorboard
|
|
635
|
+
output_text = f"Loaded tensorboard extension\n"
|
|
636
|
+
self.loaded_extensions[ext_name] = {'type': 'builtin', 'module': tensorboard}
|
|
637
|
+
except ImportError:
|
|
638
|
+
raise ImportError("TensorBoard not installed. Install with: pip install tensorboard")
|
|
639
|
+
|
|
640
|
+
else:
|
|
641
|
+
# Try to load as a Python module
|
|
642
|
+
try:
|
|
643
|
+
module = importlib.import_module(ext_name)
|
|
644
|
+
|
|
645
|
+
# Look for IPython extension API
|
|
646
|
+
if hasattr(module, 'load_ipython_extension'):
|
|
647
|
+
# Would need IPython shell instance here
|
|
648
|
+
# For now, just load the module
|
|
649
|
+
self.loaded_extensions[ext_name] = {'type': 'module', 'module': module}
|
|
650
|
+
output_text = f"Loaded extension: {ext_name}\n"
|
|
651
|
+
else:
|
|
652
|
+
# Just a regular module
|
|
653
|
+
self.loaded_extensions[ext_name] = {'type': 'module', 'module': module}
|
|
654
|
+
output_text = f"Loaded module: {ext_name} (no IPython extension API found)\n"
|
|
655
|
+
|
|
656
|
+
except ImportError as e:
|
|
657
|
+
raise ImportError(f"Could not load extension {ext_name}: {str(e)}")
|
|
658
|
+
|
|
659
|
+
result["outputs"].append({
|
|
660
|
+
"output_type": "stream",
|
|
661
|
+
"name": "stdout",
|
|
662
|
+
"text": output_text
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
if websocket:
|
|
666
|
+
await websocket.send_json({
|
|
667
|
+
"type": "stream_output",
|
|
668
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
669
|
+
})
|
|
670
|
+
|
|
671
|
+
except Exception as e:
|
|
672
|
+
result["status"] = "error"
|
|
673
|
+
result["error"] = {
|
|
674
|
+
"ename": type(e).__name__,
|
|
675
|
+
"evalue": str(e),
|
|
676
|
+
"traceback": [f"LOAD_EXT magic error: {str(e)}"]
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if websocket:
|
|
680
|
+
await websocket.send_json({
|
|
681
|
+
"type": "execution_error",
|
|
682
|
+
"data": {"error": result["error"]}
|
|
683
|
+
})
|
|
684
|
+
|
|
685
|
+
return result
|
|
686
|
+
|
|
687
|
+
async def handle_reload_ext(self, args: list, result: Dict[str, Any],
|
|
688
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
689
|
+
"""Handle %reload_ext - reload IPython extension"""
|
|
690
|
+
try:
|
|
691
|
+
if not args:
|
|
692
|
+
result["status"] = "error"
|
|
693
|
+
result["error"] = {
|
|
694
|
+
"ename": "UsageError",
|
|
695
|
+
"evalue": "%reload_ext requires an extension name",
|
|
696
|
+
"traceback": ["Usage: %reload_ext extension_name"]
|
|
697
|
+
}
|
|
698
|
+
return result
|
|
699
|
+
|
|
700
|
+
ext_name = args[0]
|
|
701
|
+
|
|
702
|
+
if ext_name not in self.loaded_extensions:
|
|
703
|
+
output_text = f"Extension {ext_name} is not loaded. Use %load_ext first.\n"
|
|
704
|
+
else:
|
|
705
|
+
# Unload and reload
|
|
706
|
+
ext_info = self.loaded_extensions[ext_name]
|
|
707
|
+
if ext_info['type'] == 'module':
|
|
708
|
+
module = ext_info['module']
|
|
709
|
+
importlib.reload(module)
|
|
710
|
+
output_text = f"Reloaded extension: {ext_name}\n"
|
|
711
|
+
else:
|
|
712
|
+
output_text = f"Reloaded extension: {ext_name}\n"
|
|
713
|
+
|
|
714
|
+
result["outputs"].append({
|
|
715
|
+
"output_type": "stream",
|
|
716
|
+
"name": "stdout",
|
|
717
|
+
"text": output_text
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
if websocket:
|
|
721
|
+
await websocket.send_json({
|
|
722
|
+
"type": "stream_output",
|
|
723
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
724
|
+
})
|
|
725
|
+
|
|
726
|
+
except Exception as e:
|
|
727
|
+
result["status"] = "error"
|
|
728
|
+
result["error"] = {
|
|
729
|
+
"ename": type(e).__name__,
|
|
730
|
+
"evalue": str(e),
|
|
731
|
+
"traceback": [f"RELOAD_EXT magic error: {str(e)}"]
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return result
|
|
735
|
+
|
|
736
|
+
async def handle_unload_ext(self, args: list, result: Dict[str, Any],
|
|
737
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
738
|
+
"""Handle %unload_ext - unload IPython extension"""
|
|
739
|
+
try:
|
|
740
|
+
if not args:
|
|
741
|
+
result["status"] = "error"
|
|
742
|
+
result["error"] = {
|
|
743
|
+
"ename": "UsageError",
|
|
744
|
+
"evalue": "%unload_ext requires an extension name",
|
|
745
|
+
"traceback": ["Usage: %unload_ext extension_name"]
|
|
746
|
+
}
|
|
747
|
+
return result
|
|
748
|
+
|
|
749
|
+
ext_name = args[0]
|
|
750
|
+
|
|
751
|
+
if ext_name in self.loaded_extensions:
|
|
752
|
+
del self.loaded_extensions[ext_name]
|
|
753
|
+
output_text = f"Unloaded extension: {ext_name}\n"
|
|
754
|
+
else:
|
|
755
|
+
output_text = f"Extension {ext_name} is not loaded.\n"
|
|
756
|
+
|
|
757
|
+
result["outputs"].append({
|
|
758
|
+
"output_type": "stream",
|
|
759
|
+
"name": "stdout",
|
|
760
|
+
"text": output_text
|
|
761
|
+
})
|
|
762
|
+
|
|
763
|
+
if websocket:
|
|
764
|
+
await websocket.send_json({
|
|
765
|
+
"type": "stream_output",
|
|
766
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
767
|
+
})
|
|
768
|
+
|
|
769
|
+
except Exception as e:
|
|
770
|
+
result["status"] = "error"
|
|
771
|
+
result["error"] = {
|
|
772
|
+
"ename": type(e).__name__,
|
|
773
|
+
"evalue": str(e),
|
|
774
|
+
"traceback": [f"UNLOAD_EXT magic error: {str(e)}"]
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return result
|
|
778
|
+
|
|
779
|
+
async def handle_timeit(self, args: list, result: Dict[str, Any],
|
|
780
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
781
|
+
"""
|
|
782
|
+
Handle %timeit - time statement execution (line magic version).
|
|
783
|
+
Usage: %timeit [-n<N>] [-r<R>] statement
|
|
784
|
+
"""
|
|
785
|
+
import timeit
|
|
786
|
+
|
|
787
|
+
# Parse arguments
|
|
788
|
+
number = None
|
|
789
|
+
repeat = 7
|
|
790
|
+
quiet = '-q' in args
|
|
791
|
+
|
|
792
|
+
# Remove flags and get statement
|
|
793
|
+
statement_parts = []
|
|
794
|
+
for arg in args:
|
|
795
|
+
if arg.startswith('-n'):
|
|
796
|
+
number = int(arg[2:]) if len(arg) > 2 else None
|
|
797
|
+
elif arg.startswith('-r'):
|
|
798
|
+
repeat = int(arg[2:]) if len(arg) > 2 else 7
|
|
799
|
+
elif arg == '-q':
|
|
800
|
+
quiet = True
|
|
801
|
+
else:
|
|
802
|
+
statement_parts.append(arg)
|
|
803
|
+
|
|
804
|
+
statement = ' '.join(statement_parts)
|
|
805
|
+
|
|
806
|
+
if not statement:
|
|
807
|
+
result["status"] = "error"
|
|
808
|
+
result["error"] = {
|
|
809
|
+
"ename": "UsageError",
|
|
810
|
+
"evalue": "%timeit requires a statement to time",
|
|
811
|
+
"traceback": ["Usage: %timeit [-n<N>] [-r<R>] statement"]
|
|
812
|
+
}
|
|
813
|
+
return result
|
|
814
|
+
|
|
815
|
+
try:
|
|
816
|
+
# Create a timer
|
|
817
|
+
timer = timeit.Timer(
|
|
818
|
+
stmt=statement,
|
|
819
|
+
globals=self.globals_dict
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
# Auto-determine number of iterations if not specified
|
|
823
|
+
if number is None:
|
|
824
|
+
for i in range(1, 10):
|
|
825
|
+
n = 10 ** i
|
|
826
|
+
try:
|
|
827
|
+
t = timer.timeit(n)
|
|
828
|
+
if t >= 0.2:
|
|
829
|
+
number = n
|
|
830
|
+
break
|
|
831
|
+
except:
|
|
832
|
+
number = 1
|
|
833
|
+
break
|
|
834
|
+
if number is None:
|
|
835
|
+
number = 10 ** 7
|
|
836
|
+
|
|
837
|
+
# Run the timing
|
|
838
|
+
all_runs = timer.repeat(repeat=repeat, number=number)
|
|
839
|
+
best = min(all_runs) / number
|
|
840
|
+
|
|
841
|
+
# Format output
|
|
842
|
+
if best < 1e-6:
|
|
843
|
+
timing_str = f"{best * 1e9:.0f} ns"
|
|
844
|
+
elif best < 1e-3:
|
|
845
|
+
timing_str = f"{best * 1e6:.0f} µs"
|
|
846
|
+
elif best < 1:
|
|
847
|
+
timing_str = f"{best * 1e3:.0f} ms"
|
|
848
|
+
else:
|
|
849
|
+
timing_str = f"{best:.2f} s"
|
|
850
|
+
|
|
851
|
+
output_text = f"{timing_str} ± {(max(all_runs) - min(all_runs)) / number * 1e6:.0f} µs per loop (mean ± std. dev. of {repeat} runs, {number} loops each)\n"
|
|
852
|
+
|
|
853
|
+
if not quiet:
|
|
854
|
+
result["outputs"].append({
|
|
855
|
+
"output_type": "stream",
|
|
856
|
+
"name": "stdout",
|
|
857
|
+
"text": output_text
|
|
858
|
+
})
|
|
859
|
+
|
|
860
|
+
if websocket:
|
|
861
|
+
await websocket.send_json({
|
|
862
|
+
"type": "stream_output",
|
|
863
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
864
|
+
})
|
|
865
|
+
|
|
866
|
+
except Exception as e:
|
|
867
|
+
result["status"] = "error"
|
|
868
|
+
result["error"] = {
|
|
869
|
+
"ename": type(e).__name__,
|
|
870
|
+
"evalue": str(e),
|
|
871
|
+
"traceback": [f"TIMEIT magic error: {str(e)}"]
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if websocket:
|
|
875
|
+
await websocket.send_json({
|
|
876
|
+
"type": "execution_error",
|
|
877
|
+
"data": {"error": result["error"]}
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
return result
|
|
881
|
+
|
|
882
|
+
async def handle_run(self, args: list, result: Dict[str, Any],
|
|
883
|
+
websocket: Optional[WebSocket] = None) -> Dict[str, Any]:
|
|
884
|
+
"""
|
|
885
|
+
Handle %run - execute Python script or notebook.
|
|
886
|
+
Usage: %run script.py [args]
|
|
887
|
+
"""
|
|
888
|
+
try:
|
|
889
|
+
if not args:
|
|
890
|
+
result["status"] = "error"
|
|
891
|
+
result["error"] = {
|
|
892
|
+
"ename": "UsageError",
|
|
893
|
+
"evalue": "%run requires a filename",
|
|
894
|
+
"traceback": ["Usage: %run filename [args]"]
|
|
895
|
+
}
|
|
896
|
+
return result
|
|
897
|
+
|
|
898
|
+
filename = args[0]
|
|
899
|
+
script_args = args[1:] if len(args) > 1 else []
|
|
900
|
+
|
|
901
|
+
# Check file exists
|
|
902
|
+
if not os.path.exists(filename):
|
|
903
|
+
raise FileNotFoundError(f"File not found: {filename}")
|
|
904
|
+
|
|
905
|
+
# Read the file
|
|
906
|
+
with open(filename, 'r') as f:
|
|
907
|
+
code = f.read()
|
|
908
|
+
|
|
909
|
+
# Update sys.argv for the script
|
|
910
|
+
old_argv = sys.argv
|
|
911
|
+
sys.argv = [filename] + script_args
|
|
912
|
+
|
|
913
|
+
try:
|
|
914
|
+
# Compile and execute
|
|
915
|
+
compiled_code = compile(code, filename, 'exec')
|
|
916
|
+
exec(compiled_code, self.globals_dict)
|
|
917
|
+
|
|
918
|
+
output_text = f"Executed: {filename}\n"
|
|
919
|
+
result["outputs"].append({
|
|
920
|
+
"output_type": "stream",
|
|
921
|
+
"name": "stdout",
|
|
922
|
+
"text": output_text
|
|
923
|
+
})
|
|
924
|
+
|
|
925
|
+
if websocket:
|
|
926
|
+
await websocket.send_json({
|
|
927
|
+
"type": "stream_output",
|
|
928
|
+
"data": {"stream": "stdout", "text": output_text}
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
finally:
|
|
932
|
+
# Restore sys.argv
|
|
933
|
+
sys.argv = old_argv
|
|
934
|
+
|
|
935
|
+
except Exception as e:
|
|
936
|
+
result["status"] = "error"
|
|
937
|
+
result["error"] = {
|
|
938
|
+
"ename": type(e).__name__,
|
|
939
|
+
"evalue": str(e),
|
|
940
|
+
"traceback": [f"RUN magic error: {str(e)}"]
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if websocket:
|
|
944
|
+
await websocket.send_json({
|
|
945
|
+
"type": "execution_error",
|
|
946
|
+
"data": {"error": result["error"]}
|
|
947
|
+
})
|
|
948
|
+
|
|
949
|
+
return result
|