more-compute 0.1.0__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.
@@ -0,0 +1,81 @@
1
+ import json
2
+ from typing import List, Dict, Any
3
+ from uuid import uuid4
4
+
5
+ class Notebook:
6
+ """Manages the state of a notebook's cells."""
7
+
8
+ def __init__(self, file_path: str = None):
9
+ self.cells: List[Dict[str, Any]] = []
10
+ self.metadata: Dict[str, Any] = {}
11
+ self.file_path = file_path
12
+ if file_path:
13
+ self.load_from_file(file_path)
14
+ else:
15
+ # Default empty notebook structure
16
+ self.cells.append({'id': self._generate_cell_id(), 'cell_type': 'code', 'source': '', 'outputs': []})
17
+
18
+ def get_notebook_data(self) -> Dict[str, Any]:
19
+ return {"cells": self.cells, "metadata": self.metadata, "file_path": self.file_path}
20
+
21
+ def add_cell(self, index: int, cell_type: str = 'code', source: str = ''):
22
+ new_cell = {'id': self._generate_cell_id(), 'cell_type': cell_type, 'source': source, 'outputs': []}
23
+ self.cells.insert(index, new_cell)
24
+
25
+ def delete_cell(self, index: int):
26
+ if 0 <= index < len(self.cells):
27
+ self.cells.pop(index)
28
+
29
+ def update_cell(self, index: int, source: str):
30
+ if 0 <= index < len(self.cells):
31
+ self.cells[index]['source'] = source
32
+
33
+ def clear_all_outputs(self):
34
+ for cell in self.cells:
35
+ cell['outputs'] = []
36
+ if 'execution_count' in cell:
37
+ cell['execution_count'] = None
38
+
39
+ def to_json(self) -> str:
40
+ # Basic notebook format
41
+ notebook_json = {
42
+ "cells": self.cells,
43
+ "metadata": self.metadata,
44
+ "nbformat": 4,
45
+ "nbformat_minor": 5
46
+ }
47
+ return json.dumps(notebook_json, indent=2)
48
+
49
+ def load_from_file(self, file_path: str):
50
+ try:
51
+ with open(file_path, 'r') as f:
52
+ data = json.load(f)
53
+ loaded_cells = data.get('cells', [])
54
+ # Ensure stable IDs for all cells (back-compat for notebooks without IDs)
55
+ self.cells = []
56
+ for cell in loaded_cells:
57
+ if not isinstance(cell, dict):
58
+ continue
59
+ if 'id' not in cell or not cell['id']:
60
+ cell['id'] = self._generate_cell_id()
61
+ self.cells.append(cell)
62
+ self.metadata = data.get('metadata', {})
63
+ self.file_path = file_path
64
+ except (FileNotFoundError, json.JSONDecodeError) as e:
65
+ print(f"Error loading notebook: {e}")
66
+ # Initialize with a default cell if loading fails
67
+ self.cells = [{'id': self._generate_cell_id(), 'cell_type': 'code', 'source': '', 'outputs': []}]
68
+ self.metadata = {}
69
+ self.file_path = file_path
70
+
71
+ def save_to_file(self, file_path: str = None):
72
+ path_to_save = file_path or self.file_path
73
+ if not path_to_save:
74
+ raise ValueError("No file path specified for saving.")
75
+
76
+ with open(path_to_save, 'w') as f:
77
+ f.write(self.to_json())
78
+ self.file_path = path_to_save
79
+
80
+ def _generate_cell_id(self) -> str:
81
+ return f"cell-{uuid4()}"
@@ -0,0 +1,209 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import signal
5
+ import base64
6
+ import io
7
+ import traceback
8
+
9
+
10
+ def _setup_worker_signals():
11
+ def _handler(signum, frame):
12
+ # Exit quickly; parent will respawn if needed
13
+ try:
14
+ sys.stdout.flush()
15
+ sys.stderr.flush()
16
+ except Exception:
17
+ pass
18
+ os._exit(0)
19
+
20
+ signal.signal(signal.SIGTERM, _handler)
21
+ try:
22
+ signal.signal(signal.SIGINT, _handler)
23
+ except Exception:
24
+ pass
25
+
26
+
27
+ class _StreamForwarder:
28
+ def __init__(self, output_queue, stream_name, cell_index):
29
+ self.output_queue = output_queue
30
+ self.stream_name = stream_name
31
+ self.buf = []
32
+ self.cell_index = cell_index
33
+
34
+ def write(self, text):
35
+ if not text:
36
+ return
37
+ # Handle carriage returns for progress bars by emitting stream_update
38
+ if '\r' in text and '\n' not in text:
39
+ # Overwrite current line
40
+ self.output_queue.put({
41
+ 'type': 'stream_update',
42
+ 'name': self.stream_name,
43
+ 'text': text.split('\r')[-1],
44
+ 'cell_index': self.cell_index,
45
+ })
46
+ return
47
+ lines = text.split('\n')
48
+ for i, line in enumerate(lines):
49
+ if i < len(lines) - 1:
50
+ self.buf.append(line)
51
+ complete = ''.join(self.buf) + '\n'
52
+ self.output_queue.put({
53
+ 'type': 'stream',
54
+ 'name': self.stream_name,
55
+ 'text': complete,
56
+ 'cell_index': self.cell_index,
57
+ })
58
+ self.buf = []
59
+ else:
60
+ self.buf.append(line)
61
+
62
+ def flush(self):
63
+ if self.buf:
64
+ self.output_queue.put({
65
+ 'type': 'stream',
66
+ 'name': self.stream_name,
67
+ 'text': ''.join(self.buf),
68
+ 'cell_index': self.cell_index,
69
+ })
70
+ self.buf = []
71
+
72
+
73
+ def _capture_matplotlib(output_queue, cell_index):
74
+ try:
75
+ import matplotlib
76
+ matplotlib.use('Agg')
77
+ import matplotlib.pyplot as plt
78
+ except Exception:
79
+ return
80
+ try:
81
+ figs = plt.get_fignums()
82
+ if not figs:
83
+ return
84
+ for num in figs:
85
+ try:
86
+ fig = plt.figure(num)
87
+ buf = io.BytesIO()
88
+ fig.savefig(buf, format='png', bbox_inches='tight')
89
+ buf.seek(0)
90
+ b64 = base64.b64encode(buf.read()).decode('ascii')
91
+ output_queue.put({
92
+ 'type': 'display_data',
93
+ 'data': {
94
+ 'image/png': b64,
95
+ 'text/plain': f'<Figure size {int(fig.get_figwidth()*fig.dpi)}x{int(fig.get_figheight()*fig.dpi)}>'
96
+ },
97
+ 'cell_index': cell_index,
98
+ })
99
+ except Exception:
100
+ continue
101
+ try:
102
+ plt.close('all')
103
+ except Exception:
104
+ pass
105
+ except Exception:
106
+ return
107
+
108
+
109
+ def worker_main(command_queue, output_queue, shutdown_event):
110
+ # New process group for POSIX
111
+ try:
112
+ if os.name != 'nt':
113
+ os.setsid()
114
+ except Exception:
115
+ pass
116
+
117
+ _setup_worker_signals()
118
+ user_globals = {"__name__": "__main__"}
119
+ user_locals = user_globals
120
+ exec_count = 0
121
+
122
+ while not shutdown_event.is_set():
123
+ try:
124
+ try:
125
+ cmd = command_queue.get(timeout=0.1)
126
+ except Exception:
127
+ continue
128
+
129
+ if not isinstance(cmd, dict):
130
+ continue
131
+
132
+ ctype = cmd.get('type')
133
+ if ctype == 'shutdown':
134
+ break
135
+ if ctype == 'execute_cell':
136
+ code = cmd.get('code', '')
137
+ cell_index = cmd.get('cell_index')
138
+ output_queue.put({'type': 'execution_start', 'cell_index': cell_index, 'execution_count': exec_count + 1})
139
+
140
+ # Redirect stdout/stderr
141
+ old_out, old_err = sys.stdout, sys.stderr
142
+ sys.stdout = _StreamForwarder(output_queue, 'stdout', cell_index)
143
+ sys.stderr = _StreamForwarder(output_queue, 'stderr', cell_index)
144
+ start = time.time()
145
+ status = 'ok'
146
+ error_payload = None
147
+ try:
148
+ compiled = compile(code, '<cell>', 'exec')
149
+ exec(compiled, user_globals, user_locals)
150
+ # Try to evaluate last expression (simple heuristic)
151
+ lines = code.strip().split('\n')
152
+ if lines:
153
+ last = lines[-1].strip()
154
+ if last and not last.startswith('#'):
155
+ try:
156
+ result = eval(last, user_globals, user_locals)
157
+ except Exception:
158
+ result = None
159
+ else:
160
+ if result is not None:
161
+ output_queue.put({
162
+ 'type': 'execute_result',
163
+ 'cell_index': cell_index,
164
+ 'execution_count': exec_count + 1,
165
+ 'data': {'text/plain': repr(result)}
166
+ })
167
+ _capture_matplotlib(output_queue, cell_index)
168
+ except KeyboardInterrupt:
169
+ status = 'error'
170
+ error_payload = {
171
+ 'ename': 'KeyboardInterrupt',
172
+ 'evalue': 'Execution interrupted by user',
173
+ 'traceback': []
174
+ }
175
+ except Exception as exc:
176
+ status = 'error'
177
+ tb = traceback.format_exc().split('\n')
178
+ error_payload = {
179
+ 'ename': type(exc).__name__,
180
+ 'evalue': str(exc),
181
+ 'traceback': tb
182
+ }
183
+ finally:
184
+ try:
185
+ sys.stdout.flush(); sys.stderr.flush()
186
+ except Exception:
187
+ pass
188
+ sys.stdout, sys.stderr = old_out, old_err
189
+
190
+ exec_count += 1
191
+ duration_ms = f"{(time.time()-start)*1000:.1f}ms"
192
+ if error_payload:
193
+ output_queue.put({'type': 'execution_error', 'cell_index': cell_index, 'error': error_payload})
194
+ output_queue.put({
195
+ 'type': 'execution_complete',
196
+ 'cell_index': cell_index,
197
+ 'result': {
198
+ 'status': status,
199
+ 'execution_count': exec_count,
200
+ 'execution_time': duration_ms,
201
+ 'outputs': [],
202
+ 'error': error_payload,
203
+ }
204
+ })
205
+ except Exception:
206
+ # Avoid worker crash on unexpected errors
207
+ continue
208
+
209
+