more-compute 0.4.1__py3-none-any.whl → 0.4.3__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.
kernel_run.py CHANGED
@@ -1,8 +1,20 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
+ import sys
4
+ if sys.version_info < (3, 10):
5
+ print(f"Error: more-compute requires Python 3.10 or higher.")
6
+ print(f"You are using Python {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
7
+ print()
8
+ print("To fix this:")
9
+ print(" 1. Uninstall: uv tool uninstall more-compute")
10
+ print(" 2. Reinstall with specific version: uv tool install more-compute==0.4.1")
11
+ print(" (uv will automatically download Python 3.10+ for you)")
12
+ print()
13
+ print("Or upgrade your system Python: https://www.python.org/downloads/")
14
+ sys.exit(1)
15
+
3
16
  import argparse
4
17
  import subprocess
5
- import sys
6
18
  import os
7
19
  import time
8
20
  import signal
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: An interactive notebook environment for local and GPU computing
5
5
  Home-page: https://github.com/DannyMang/MORECOMPUTE
6
6
  Author: MoreCompute Team
7
- Author-email: MoreCompute Team <hello@morecompute.dev>
7
+ Author-email: Daniel Ung <daniel.haidang.ung@gmail.com>
8
8
  License: MIT
9
9
  Project-URL: Homepage, https://github.com/DannyMang/MORECOMPUTE
10
10
  Project-URL: Repository, https://github.com/DannyMang/MORECOMPUTE
@@ -1,4 +1,4 @@
1
- kernel_run.py,sha256=tY8n3U4-mB3PYwVWDEOe5x0v67iXMOWpIrWNFqcm7JE,21540
1
+ kernel_run.py,sha256=IWLro2qepvFrvM-qYY4-sOoMW_u6N_huh0KKBDydPmY,22111
2
2
  frontend/.gitignore,sha256=IH4mX_SQH5rZ-W2M4IUw4E-fxgCBVHKmbQpEYJbWVM0,480
3
3
  frontend/README.md,sha256=YLVf9995r3JZD5UkII5GZCvDK9wXXNrUE0loHA4vlY8,1450
4
4
  frontend/__init__.py,sha256=L5SAOdfDfKqlgEVCvYQQRDZBTlCxutZKSpJp4018IG4,100
@@ -63,11 +63,11 @@ frontend/public/fonts/Fira.ttf,sha256=dbSM4W7Drd9n_EkfXq8P31KuxbjZ1wtuFiZ8aFebvT
63
63
  frontend/public/fonts/Tiempos.woff2,sha256=h83bJKvAK301wXCMIvK7ZG5j0H2K3tzAfgo0yk4z8OE,13604
64
64
  frontend/public/fonts/VeraMono.ttf,sha256=2kKB3H2xej385kpiztkodcWJU0AFXsi6JKORTrl7NJ0,49224
65
65
  frontend/types/notebook.ts,sha256=v23RaZe6H3lU5tq6sqnJDPxC2mu0NZFDCJfiN0mgvSs,1359
66
- more_compute-0.4.1.dist-info/licenses/LICENSE,sha256=0Ot-XIetYt06iay6IhtpJkruD-cLZtjyv7_aIEE-oSc,1073
66
+ more_compute-0.4.3.dist-info/licenses/LICENSE,sha256=UQuJtJ2v98w8mXozOPmE8nkNc_8BFY-SVVBgTA327z0,1067
67
67
  morecompute/__init__.py,sha256=pcMVq8Q7qb42AOn7tqgoZJOi3epDDBnEriiv2WVKnXY,87
68
- morecompute/__version__.py,sha256=pMtTmSUht-XtbR_7Doz6bsQqopJJd8rZ8I8zy2HwwoA,22
68
+ morecompute/__version__.py,sha256=Nyg0pmk5ea9-SLCAFEIF96ByFx4-TJFtrqYPN-Zn6g4,22
69
69
  morecompute/cli.py,sha256=kVvzvPBqF8xO6UuhU_-TBn99nKwJ405R2mAS6zU0KBc,734
70
- morecompute/notebook.py,sha256=RubIXps925vCFgHznkW0QvvW0I11p6IGDDOrhnZ4jYs,6824
70
+ morecompute/notebook.py,sha256=KGHORGAAZahT-TyX9HYuSc-b6TmfGgoi92FhRMK--Yg,7730
71
71
  morecompute/process_worker.py,sha256=KsE3r-XpkYGuyO4w3t54VKkD51LfNHAZc3TYattMtrg,7185
72
72
  morecompute/server.py,sha256=mcJwIvT9QHfpKuAq5jP2SW5hPjFB_rySevgQvOHuud8,41689
73
73
  morecompute/execution/__init__.py,sha256=jPmBmq8BZWbUEY9XFSpqt5FkgX04uNS10WnUlr7Rnms,134
@@ -88,16 +88,16 @@ morecompute/utils/cell_magics.py,sha256=YLxltTBprCA8jfgsjqf7Shnk4NCmGKIp3aV5_CYk
88
88
  morecompute/utils/config_util.py,sha256=I90om4Wf4BODc5Gy9u8Tu34AcqpkfkGAKQO6JE_NMzU,1940
89
89
  morecompute/utils/error_utils.py,sha256=e50WLFdD6ngIC30xAgrzdTYtD8tPOIFkKAAh_sPbK0I,11667
90
90
  morecompute/utils/line_magics.py,sha256=kTutYBPAWoURY_pk8HXQ38IP712M2rBBfUg3oN8VrP0,33740
91
- morecompute/utils/notebook_converter.py,sha256=aI9JdgPO8zpE0vYcBGlMEvQT2rLmZtjP_VD5bUn80bY,4229
91
+ morecompute/utils/notebook_converter.py,sha256=_V-iG060Rr04oqk__cg1LbfV3C7uR1Rsh504WNZ218Q,9756
92
92
  morecompute/utils/notebook_util.py,sha256=3hH94dtXvhizRVTU9a2b38m_51Y4igoXpkjAXUqpVBQ,1353
93
- morecompute/utils/py_percent_parser.py,sha256=_CVB7uP8Qu8X_yIGKv-k3Lhpw7saH67VofYvRDfGGw8,5087
93
+ morecompute/utils/py_percent_parser.py,sha256=c2AUOcXjGxmBWaYy0kk-pbNVzs-6YFagMVuVGQlYjY0,5388
94
94
  morecompute/utils/python_environment_util.py,sha256=l8WWWPwKbypknw8GwL22NXCji5i1FOy1vWG47J6og4g,7441
95
95
  morecompute/utils/shell_utils.py,sha256=fGFLhQLZU-lmMGALbbS-fKPkhQtmMhZ1FkgQ3TeoFhA,1917
96
96
  morecompute/utils/special_commands.py,sha256=nf2nVea5SyFqpulNYrnRx7anU4-G-e5_kLJ0PLtEi3Q,22030
97
97
  morecompute/utils/system_environment_util.py,sha256=32mQRubo0i4X61o-825T7m-eUSidcEp07qkInP1sWZA,4774
98
98
  morecompute/utils/zmq_util.py,sha256=tx7-iS04UN69OFtBzkxcEnRhT7xtI9EzRnrZ_nsH_O0,1889
99
- more_compute-0.4.1.dist-info/METADATA,sha256=mEkYTxCnqN2pdj6uOlVb9KFExCsavjj6HyACH_26zXo,3868
100
- more_compute-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
- more_compute-0.4.1.dist-info/entry_points.txt,sha256=xp7z9eRPNRM4oxkZZVlyXkhkSjN1AjoYI_B7qpDJ1bI,49
102
- more_compute-0.4.1.dist-info/top_level.txt,sha256=Tamm6ADzjwaQa1z27O7Izcyhyt9f0gVjMv1_tC810aI,32
103
- more_compute-0.4.1.dist-info/RECORD,,
99
+ more_compute-0.4.3.dist-info/METADATA,sha256=U9KhKWUCeCulBSndkv55vtyDzwdIzKIt_VuHSt0Bh50,3869
100
+ more_compute-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
101
+ more_compute-0.4.3.dist-info/entry_points.txt,sha256=xp7z9eRPNRM4oxkZZVlyXkhkSjN1AjoYI_B7qpDJ1bI,49
102
+ more_compute-0.4.3.dist-info/top_level.txt,sha256=Tamm6ADzjwaQa1z27O7Izcyhyt9f0gVjMv1_tC810aI,32
103
+ more_compute-0.4.3.dist-info/RECORD,,
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 MoreCompute Team
3
+ Copyright (c) 2025 Daniel Ung
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1 +1 @@
1
- __version__ = "0.4.1"
1
+ __version__ = "0.4.3"
morecompute/notebook.py CHANGED
@@ -3,6 +3,7 @@ from pathlib import Path
3
3
  from typing import List, Dict, Any
4
4
  from uuid import uuid4
5
5
  from .utils.py_percent_parser import parse_py_percent, generate_py_percent
6
+ from .utils.notebook_converter import detect_colab_format, parse_colab_py
6
7
 
7
8
  class Notebook:
8
9
  """Manages the state of a notebook's cells."""
@@ -92,11 +93,29 @@ class Notebook:
92
93
 
93
94
  # Check file extension
94
95
  if path.suffix == '.py':
95
- # Load .py file with py:percent format
96
+ # Load .py file - auto-detect format
96
97
  with open(file_path, 'r', encoding='utf-8') as f:
97
98
  content = f.read()
98
99
 
99
- data = parse_py_percent(content)
100
+ # Detect if it's Colab format (docstrings) or py:percent format (# %%)
101
+ if detect_colab_format(content):
102
+ print(f" Detected Colab format - auto-converting to py:percent")
103
+ # Parse Colab format (docstrings as markdown)
104
+ loaded_cells = parse_colab_py(content)
105
+ data = {
106
+ 'cells': loaded_cells,
107
+ 'metadata': {
108
+ 'kernelspec': {
109
+ 'display_name': 'Python 3',
110
+ 'language': 'python',
111
+ 'name': 'python3'
112
+ }
113
+ }
114
+ }
115
+ else:
116
+ # Parse py:percent format (# %% markers)
117
+ data = parse_py_percent(content)
118
+
100
119
  loaded_cells = data.get('cells', [])
101
120
 
102
121
  # Ensure stable IDs for all cells
@@ -3,7 +3,7 @@
3
3
  import json
4
4
  import re
5
5
  from pathlib import Path
6
- from typing import List, Set
6
+ from typing import List, Set, Dict
7
7
  from .py_percent_parser import generate_py_percent, parse_py_percent
8
8
 
9
9
 
@@ -97,10 +97,157 @@ def convert_ipynb_to_py(ipynb_path: Path, output_path: Path, include_uv_deps: bo
97
97
  print(f" Run with: more-compute {output_path.name}")
98
98
 
99
99
 
100
+ def detect_colab_format(content: str) -> bool:
101
+ """
102
+ Detect if a .py file is in Colab export format (docstrings as markdown).
103
+
104
+ Args:
105
+ content: Raw .py file content
106
+
107
+ Returns:
108
+ True if appears to be Colab format, False otherwise
109
+ """
110
+ # Check for actual cell markers (# %% at start of line)
111
+ # Must be ONLY # %% optionally followed by [markdown] or whitespace
112
+ # NOT # %%capture, # %%time, etc. (IPython magics)
113
+ has_cell_markers = bool(re.search(r'^\s*# %%\s*(?:\[markdown\])?\s*$', content, re.MULTILINE))
114
+
115
+ # If it has # %% markers, it's NOT Colab format (it's py:percent)
116
+ # Even if it has a Colab header comment!
117
+ if has_cell_markers:
118
+ return False
119
+
120
+ # Check for multi-line docstrings (Colab's markdown format)
121
+ has_docstrings = '"""' in content
122
+
123
+ # Colab format: has docstrings and no cell markers
124
+ return has_docstrings
125
+
126
+
127
+ def parse_colab_py(content: str) -> List[Dict]:
128
+ """
129
+ Parse Colab-exported .py file into cell structure.
130
+
131
+ Colab format uses:
132
+ - Multi-line docstrings ('''..''' or \"\"\"...\"\"\") for markdown cells
133
+ - Regular Python code for code cells
134
+
135
+ Args:
136
+ content: Raw .py file content from Colab export
137
+
138
+ Returns:
139
+ List of cell dicts with 'cell_type' and 'source'
140
+ """
141
+ cells = []
142
+
143
+ # Split on docstring boundaries
144
+ # Pattern matches both ''' and """ with optional content
145
+ pattern = r'("""[\s\S]*?"""|\'\'\'[\s\S]*?\'\'\')'
146
+ parts = re.split(pattern, content)
147
+
148
+ for part in parts:
149
+ part = part.strip()
150
+ if not part:
151
+ continue
152
+
153
+ # Check if this is a docstring (markdown)
154
+ if (part.startswith('"""') and part.endswith('"""')) or \
155
+ (part.startswith("'''") and part.endswith("'''")):
156
+ # It's a markdown cell
157
+ # Remove the triple quotes
158
+ markdown_content = part[3:-3].strip()
159
+
160
+ # Skip empty markdown cells
161
+ if markdown_content:
162
+ cells.append({
163
+ 'cell_type': 'markdown',
164
+ 'source': markdown_content,
165
+ 'metadata': {}
166
+ })
167
+ else:
168
+ # It's a code cell
169
+ # Skip commented out code and special markers
170
+ if part and not part.startswith('# Commented out IPython magic'):
171
+ cells.append({
172
+ 'cell_type': 'code',
173
+ 'source': part,
174
+ 'metadata': {},
175
+ 'execution_count': None,
176
+ 'outputs': []
177
+ })
178
+
179
+ return cells
180
+
181
+
182
+ def convert_colab_py_to_py_percent(input_path: Path, output_path: Path, include_uv_deps: bool = True) -> None:
183
+ """
184
+ Convert Colab-exported .py file to py:percent format (# %% markers).
185
+
186
+ Args:
187
+ input_path: Path to Colab .py file
188
+ output_path: Path to output .py file with # %% markers
189
+ include_uv_deps: Whether to extract and add UV inline script dependencies
190
+ """
191
+ # Read Colab .py file
192
+ with open(input_path, 'r', encoding='utf-8') as f:
193
+ content = f.read()
194
+
195
+ # Detect format
196
+ if not detect_colab_format(content):
197
+ print(f"Warning: {input_path.name} doesn't appear to be in Colab format")
198
+ print(f" (Already has # %% markers or missing docstrings)")
199
+ return
200
+
201
+ # Parse Colab format into cells
202
+ cells = parse_colab_py(content)
203
+
204
+ if not cells:
205
+ print(f"Error: No cells found in {input_path.name}")
206
+ return
207
+
208
+ # Extract dependencies if requested
209
+ header_lines = []
210
+ if include_uv_deps:
211
+ # Create a temporary notebook structure to extract dependencies
212
+ temp_notebook = {'cells': cells}
213
+ dependencies = extract_pip_dependencies(temp_notebook)
214
+
215
+ if dependencies:
216
+ header_lines.append('# /// script')
217
+ header_lines.append('# dependencies = [')
218
+ for dep in sorted(dependencies):
219
+ header_lines.append(f'# "{dep}",')
220
+ header_lines.append('# ]')
221
+ header_lines.append('# ///')
222
+ header_lines.append('')
223
+
224
+ # Generate py:percent format
225
+ py_content = generate_py_percent(cells)
226
+
227
+ # Combine header and content
228
+ if header_lines:
229
+ final_content = '\n'.join(header_lines) + '\n' + py_content
230
+ else:
231
+ final_content = py_content
232
+
233
+ # Write output
234
+ with open(output_path, 'w', encoding='utf-8') as f:
235
+ f.write(final_content)
236
+
237
+ print(f"✓ Converted Colab format {input_path.name} → {output_path.name}")
238
+ print(f" Format: Colab docstrings → py:percent (# %%) markers")
239
+
240
+ if include_uv_deps and dependencies:
241
+ print(f" Found dependencies: {', '.join(sorted(dependencies))}")
242
+ print(f" Run with: more-compute {output_path.name}")
243
+
244
+
100
245
  def convert_py_to_ipynb(py_path: Path, output_path: Path) -> None:
101
246
  """
102
247
  Convert .py notebook to .ipynb format.
103
248
 
249
+ Automatically detects format (Colab, VSCode, JupyterLab).
250
+
104
251
  Args:
105
252
  py_path: Path to input .py file
106
253
  output_path: Path to output .ipynb file
@@ -109,8 +256,29 @@ def convert_py_to_ipynb(py_path: Path, output_path: Path) -> None:
109
256
  with open(py_path, 'r', encoding='utf-8') as f:
110
257
  py_content = f.read()
111
258
 
112
- # Parse py:percent format to notebook structure
113
- notebook_data = parse_py_percent(py_content)
259
+ # Detect format and parse accordingly
260
+ if detect_colab_format(py_content):
261
+ # Parse Colab format (docstrings as markdown)
262
+ cells = parse_colab_py(py_content)
263
+ notebook_data = {
264
+ 'cells': cells,
265
+ 'metadata': {
266
+ 'kernelspec': {
267
+ 'display_name': 'Python 3',
268
+ 'language': 'python',
269
+ 'name': 'python3'
270
+ },
271
+ 'language_info': {
272
+ 'name': 'python',
273
+ 'version': '3.8.0'
274
+ }
275
+ },
276
+ 'nbformat': 4,
277
+ 'nbformat_minor': 4
278
+ }
279
+ else:
280
+ # Parse py:percent format (# %% or # In[N]:)
281
+ notebook_data = parse_py_percent(py_content)
114
282
 
115
283
  # Ensure source is in list format (Jupyter notebook standard)
116
284
  for cell in notebook_data.get('cells', []):
@@ -8,6 +8,10 @@ def parse_py_percent(content: str) -> Dict:
8
8
  """
9
9
  Parse py:percent format Python file into notebook structure.
10
10
 
11
+ Supports multiple formats:
12
+ # %% - VSCode/PyCharm format
13
+ # In[N]: - JupyterLab export format
14
+
11
15
  Format:
12
16
  # %%
13
17
  code cell content
@@ -23,9 +27,15 @@ def parse_py_percent(content: str) -> Dict:
23
27
  """
24
28
  cells = []
25
29
 
26
- # Split by cell markers (# %%)
27
- # Keep the marker in the split to determine cell type
28
- parts = re.split(r'(# %%.*?\n)', content)
30
+ # Check if using JupyterLab In[] format
31
+ has_in_markers = bool(re.search(r'# In\[\d+\]:', content))
32
+
33
+ if has_in_markers:
34
+ # Parse JupyterLab # In[N]: format
35
+ parts = re.split(r'(# In\[\d+\]:.*?\n)', content)
36
+ else:
37
+ # Parse VSCode # %% format
38
+ parts = re.split(r'(# %%.*?\n)', content)
29
39
 
30
40
  # First part before any cell marker (usually imports/metadata)
31
41
  if parts[0].strip():