more-compute 0.4.2__tar.gz → 0.4.3__tar.gz

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.
Files changed (112) hide show
  1. {more_compute-0.4.2/more_compute.egg-info → more_compute-0.4.3}/PKG-INFO +1 -1
  2. {more_compute-0.4.2 → more_compute-0.4.3/more_compute.egg-info}/PKG-INFO +1 -1
  3. more_compute-0.4.3/morecompute/__version__.py +1 -0
  4. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/notebook.py +21 -2
  5. more_compute-0.4.3/morecompute/utils/notebook_converter.py +297 -0
  6. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/py_percent_parser.py +13 -3
  7. more_compute-0.4.2/morecompute/__version__.py +0 -1
  8. more_compute-0.4.2/morecompute/utils/notebook_converter.py +0 -129
  9. {more_compute-0.4.2 → more_compute-0.4.3}/LICENSE +0 -0
  10. {more_compute-0.4.2 → more_compute-0.4.3}/MANIFEST.in +0 -0
  11. {more_compute-0.4.2 → more_compute-0.4.3}/README.md +0 -0
  12. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/.gitignore +0 -0
  13. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/README.md +0 -0
  14. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/__init__.py +0 -0
  15. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/app/favicon.ico +0 -0
  16. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/app/globals.css +0 -0
  17. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/app/layout.tsx +0 -0
  18. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/app/page.tsx +0 -0
  19. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/Notebook.tsx +0 -0
  20. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/cell/AddCellButton.tsx +0 -0
  21. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/cell/CellButton.tsx +0 -0
  22. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/cell/MonacoCell.tsx +0 -0
  23. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/layout/ConnectionBanner.tsx +0 -0
  24. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/layout/Sidebar.tsx +0 -0
  25. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/modals/ConfirmModal.tsx +0 -0
  26. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/modals/ErrorModal.tsx +0 -0
  27. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/modals/SuccessModal.tsx +0 -0
  28. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/output/CellOutput.tsx +0 -0
  29. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/output/ErrorDisplay.tsx +0 -0
  30. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/output/MarkdownRenderer.tsx +0 -0
  31. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/popups/ComputePopup.tsx +0 -0
  32. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/popups/FilterPopup.tsx +0 -0
  33. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/popups/FolderPopup.tsx +0 -0
  34. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/popups/MetricsPopup.tsx +0 -0
  35. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/popups/PackagesPopup.tsx +0 -0
  36. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/components/popups/SettingsPopup.tsx +0 -0
  37. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/contexts/PodWebSocketContext.tsx +0 -0
  38. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/eslint.config.mjs +0 -0
  39. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/lib/api.ts +0 -0
  40. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/lib/monaco-themes.ts +0 -0
  41. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/lib/settings.ts +0 -0
  42. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/lib/themes.json +0 -0
  43. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/lib/websocket-native.ts +0 -0
  44. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/lib/websocket.ts +0 -0
  45. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/next-env.d.ts +0 -0
  46. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/next.config.mjs +0 -0
  47. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/next.config.ts +0 -0
  48. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/package-lock.json +0 -0
  49. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/package.json +0 -0
  50. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/postcss.config.mjs +0 -0
  51. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/add.svg +0 -0
  52. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/check.svg +0 -0
  53. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/copy.svg +0 -0
  54. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/folder.svg +0 -0
  55. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/metric.svg +0 -0
  56. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/packages.svg +0 -0
  57. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/play.svg +0 -0
  58. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/python.svg +0 -0
  59. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/setting.svg +0 -0
  60. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/stop.svg +0 -0
  61. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/trash.svg +0 -0
  62. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/up-down.svg +0 -0
  63. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/assets/icons/x.svg +0 -0
  64. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/file.svg +0 -0
  65. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/fonts/Fira.ttf +0 -0
  66. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/fonts/Tiempos.woff2 +0 -0
  67. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/fonts/VeraMono.ttf +0 -0
  68. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/globe.svg +0 -0
  69. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/next.svg +0 -0
  70. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/vercel.svg +0 -0
  71. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/public/window.svg +0 -0
  72. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/styling_README.md +0 -0
  73. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/tailwind.config.ts +0 -0
  74. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/tsconfig.json +0 -0
  75. {more_compute-0.4.2 → more_compute-0.4.3}/frontend/types/notebook.ts +0 -0
  76. {more_compute-0.4.2 → more_compute-0.4.3}/kernel_run.py +0 -0
  77. {more_compute-0.4.2 → more_compute-0.4.3}/more_compute.egg-info/SOURCES.txt +0 -0
  78. {more_compute-0.4.2 → more_compute-0.4.3}/more_compute.egg-info/dependency_links.txt +0 -0
  79. {more_compute-0.4.2 → more_compute-0.4.3}/more_compute.egg-info/entry_points.txt +0 -0
  80. {more_compute-0.4.2 → more_compute-0.4.3}/more_compute.egg-info/requires.txt +0 -0
  81. {more_compute-0.4.2 → more_compute-0.4.3}/more_compute.egg-info/top_level.txt +0 -0
  82. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/__init__.py +0 -0
  83. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/cli.py +0 -0
  84. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/execution/__init__.py +0 -0
  85. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/execution/__main__.py +0 -0
  86. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/execution/executor.py +0 -0
  87. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/execution/worker.py +0 -0
  88. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/models/__init__.py +0 -0
  89. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/models/api_models.py +0 -0
  90. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/process_worker.py +0 -0
  91. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/server.py +0 -0
  92. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/services/data_manager.py +0 -0
  93. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/services/lsp_service.py +0 -0
  94. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/services/pod_manager.py +0 -0
  95. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/services/pod_monitor.py +0 -0
  96. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/services/prime_intellect.py +0 -0
  97. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/static/styles.css +0 -0
  98. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/__init__.py +0 -0
  99. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/cache_util.py +0 -0
  100. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/cell_magics.py +0 -0
  101. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/config_util.py +0 -0
  102. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/error_utils.py +0 -0
  103. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/line_magics.py +0 -0
  104. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/notebook_util.py +0 -0
  105. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/python_environment_util.py +0 -0
  106. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/shell_utils.py +0 -0
  107. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/special_commands.py +0 -0
  108. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/system_environment_util.py +0 -0
  109. {more_compute-0.4.2 → more_compute-0.4.3}/morecompute/utils/zmq_util.py +0 -0
  110. {more_compute-0.4.2 → more_compute-0.4.3}/pyproject.toml +0 -0
  111. {more_compute-0.4.2 → more_compute-0.4.3}/setup.cfg +0 -0
  112. {more_compute-0.4.2 → more_compute-0.4.3}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.4.2
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: more-compute
3
- Version: 0.4.2
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
@@ -0,0 +1 @@
1
+ __version__ = "0.4.3"
@@ -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
@@ -0,0 +1,297 @@
1
+ """Converter utilities for notebook formats."""
2
+
3
+ import json
4
+ import re
5
+ from pathlib import Path
6
+ from typing import List, Set, Dict
7
+ from .py_percent_parser import generate_py_percent, parse_py_percent
8
+
9
+
10
+ def extract_pip_dependencies(notebook_data: dict) -> Set[str]:
11
+ """
12
+ Extract package names from !pip install and %pip install commands.
13
+
14
+ Args:
15
+ notebook_data: Parsed notebook JSON
16
+
17
+ Returns:
18
+ Set of package names
19
+ """
20
+ packages = set()
21
+
22
+ for cell in notebook_data.get('cells', []):
23
+ if cell.get('cell_type') != 'code':
24
+ continue
25
+
26
+ source = cell.get('source', [])
27
+ if isinstance(source, list):
28
+ source = ''.join(source)
29
+
30
+ # Match: !pip install package1 package2
31
+ # Match: %pip install package1 package2
32
+ pip_pattern = r'[!%]pip\s+install\s+([^\n]+)'
33
+ matches = re.finditer(pip_pattern, source)
34
+
35
+ for match in matches:
36
+ install_line = match.group(1)
37
+ # Remove common flags
38
+ install_line = re.sub(r'--[^\s]+\s*', '', install_line)
39
+ install_line = re.sub(r'-[qU]\s*', '', install_line)
40
+
41
+ # Extract package names (handle package==version format)
42
+ parts = install_line.split()
43
+ for part in parts:
44
+ part = part.strip()
45
+ if part and not part.startswith('-'):
46
+ packages.add(part)
47
+
48
+ return packages
49
+
50
+
51
+ def convert_ipynb_to_py(ipynb_path: Path, output_path: Path, include_uv_deps: bool = True) -> None:
52
+ """
53
+ Convert .ipynb notebook to .py format with py:percent cell markers.
54
+
55
+ Args:
56
+ ipynb_path: Path to input .ipynb file
57
+ output_path: Path to output .py file
58
+ include_uv_deps: Whether to add UV inline script dependencies
59
+ """
60
+ # Read notebook
61
+ with open(ipynb_path, 'r', encoding='utf-8') as f:
62
+ notebook_data = json.load(f)
63
+
64
+ cells = notebook_data.get('cells', [])
65
+
66
+ # Generate UV dependencies header if requested
67
+ header_lines = []
68
+ if include_uv_deps:
69
+ dependencies = extract_pip_dependencies(notebook_data)
70
+ if dependencies:
71
+ header_lines.append('# /// script')
72
+ header_lines.append('# dependencies = [')
73
+ for dep in sorted(dependencies):
74
+ header_lines.append(f'# "{dep}",')
75
+ header_lines.append('# ]')
76
+ header_lines.append('# ///')
77
+ header_lines.append('')
78
+
79
+ # Generate py:percent format
80
+ py_content = generate_py_percent(cells)
81
+
82
+ # Combine header and content
83
+ if header_lines:
84
+ final_content = '\n'.join(header_lines) + '\n' + py_content
85
+ else:
86
+ final_content = py_content
87
+
88
+ # Write output
89
+ with open(output_path, 'w', encoding='utf-8') as f:
90
+ f.write(final_content)
91
+
92
+ print(f"✓ Converted {ipynb_path.name} → {output_path.name}")
93
+
94
+ # Show dependencies if found
95
+ if include_uv_deps and dependencies:
96
+ print(f" Found dependencies: {', '.join(sorted(dependencies))}")
97
+ print(f" Run with: more-compute {output_path.name}")
98
+
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
+
245
+ def convert_py_to_ipynb(py_path: Path, output_path: Path) -> None:
246
+ """
247
+ Convert .py notebook to .ipynb format.
248
+
249
+ Automatically detects format (Colab, VSCode, JupyterLab).
250
+
251
+ Args:
252
+ py_path: Path to input .py file
253
+ output_path: Path to output .ipynb file
254
+ """
255
+ # Read .py file
256
+ with open(py_path, 'r', encoding='utf-8') as f:
257
+ py_content = f.read()
258
+
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)
282
+
283
+ # Ensure source is in list format (Jupyter notebook standard)
284
+ for cell in notebook_data.get('cells', []):
285
+ source = cell.get('source', '')
286
+ if isinstance(source, str):
287
+ # Split into lines and keep newlines (Jupyter format)
288
+ lines = source.split('\n')
289
+ # Add \n to each line except the last
290
+ cell['source'] = [line + '\n' for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
291
+
292
+ # Write .ipynb file
293
+ with open(output_path, 'w', encoding='utf-8') as f:
294
+ json.dump(notebook_data, f, indent=1, ensure_ascii=False)
295
+
296
+ print(f"Converted {py_path.name} -> {output_path.name}")
297
+ print(f" Upload to Google Colab or open in Jupyter")
@@ -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():
@@ -1 +0,0 @@
1
- __version__ = "0.4.2"
@@ -1,129 +0,0 @@
1
- """Converter utilities for notebook formats."""
2
-
3
- import json
4
- import re
5
- from pathlib import Path
6
- from typing import List, Set
7
- from .py_percent_parser import generate_py_percent, parse_py_percent
8
-
9
-
10
- def extract_pip_dependencies(notebook_data: dict) -> Set[str]:
11
- """
12
- Extract package names from !pip install and %pip install commands.
13
-
14
- Args:
15
- notebook_data: Parsed notebook JSON
16
-
17
- Returns:
18
- Set of package names
19
- """
20
- packages = set()
21
-
22
- for cell in notebook_data.get('cells', []):
23
- if cell.get('cell_type') != 'code':
24
- continue
25
-
26
- source = cell.get('source', [])
27
- if isinstance(source, list):
28
- source = ''.join(source)
29
-
30
- # Match: !pip install package1 package2
31
- # Match: %pip install package1 package2
32
- pip_pattern = r'[!%]pip\s+install\s+([^\n]+)'
33
- matches = re.finditer(pip_pattern, source)
34
-
35
- for match in matches:
36
- install_line = match.group(1)
37
- # Remove common flags
38
- install_line = re.sub(r'--[^\s]+\s*', '', install_line)
39
- install_line = re.sub(r'-[qU]\s*', '', install_line)
40
-
41
- # Extract package names (handle package==version format)
42
- parts = install_line.split()
43
- for part in parts:
44
- part = part.strip()
45
- if part and not part.startswith('-'):
46
- packages.add(part)
47
-
48
- return packages
49
-
50
-
51
- def convert_ipynb_to_py(ipynb_path: Path, output_path: Path, include_uv_deps: bool = True) -> None:
52
- """
53
- Convert .ipynb notebook to .py format with py:percent cell markers.
54
-
55
- Args:
56
- ipynb_path: Path to input .ipynb file
57
- output_path: Path to output .py file
58
- include_uv_deps: Whether to add UV inline script dependencies
59
- """
60
- # Read notebook
61
- with open(ipynb_path, 'r', encoding='utf-8') as f:
62
- notebook_data = json.load(f)
63
-
64
- cells = notebook_data.get('cells', [])
65
-
66
- # Generate UV dependencies header if requested
67
- header_lines = []
68
- if include_uv_deps:
69
- dependencies = extract_pip_dependencies(notebook_data)
70
- if dependencies:
71
- header_lines.append('# /// script')
72
- header_lines.append('# dependencies = [')
73
- for dep in sorted(dependencies):
74
- header_lines.append(f'# "{dep}",')
75
- header_lines.append('# ]')
76
- header_lines.append('# ///')
77
- header_lines.append('')
78
-
79
- # Generate py:percent format
80
- py_content = generate_py_percent(cells)
81
-
82
- # Combine header and content
83
- if header_lines:
84
- final_content = '\n'.join(header_lines) + '\n' + py_content
85
- else:
86
- final_content = py_content
87
-
88
- # Write output
89
- with open(output_path, 'w', encoding='utf-8') as f:
90
- f.write(final_content)
91
-
92
- print(f"✓ Converted {ipynb_path.name} → {output_path.name}")
93
-
94
- # Show dependencies if found
95
- if include_uv_deps and dependencies:
96
- print(f" Found dependencies: {', '.join(sorted(dependencies))}")
97
- print(f" Run with: more-compute {output_path.name}")
98
-
99
-
100
- def convert_py_to_ipynb(py_path: Path, output_path: Path) -> None:
101
- """
102
- Convert .py notebook to .ipynb format.
103
-
104
- Args:
105
- py_path: Path to input .py file
106
- output_path: Path to output .ipynb file
107
- """
108
- # Read .py file
109
- with open(py_path, 'r', encoding='utf-8') as f:
110
- py_content = f.read()
111
-
112
- # Parse py:percent format to notebook structure
113
- notebook_data = parse_py_percent(py_content)
114
-
115
- # Ensure source is in list format (Jupyter notebook standard)
116
- for cell in notebook_data.get('cells', []):
117
- source = cell.get('source', '')
118
- if isinstance(source, str):
119
- # Split into lines and keep newlines (Jupyter format)
120
- lines = source.split('\n')
121
- # Add \n to each line except the last
122
- cell['source'] = [line + '\n' for line in lines[:-1]] + ([lines[-1]] if lines[-1] else [])
123
-
124
- # Write .ipynb file
125
- with open(output_path, 'w', encoding='utf-8') as f:
126
- json.dump(notebook_data, f, indent=1, ensure_ascii=False)
127
-
128
- print(f"Converted {py_path.name} -> {output_path.name}")
129
- print(f" Upload to Google Colab or open in Jupyter")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes