mcli-framework 7.10.0__py3-none-any.whl → 7.10.2__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.

Potentially problematic release.


This version of mcli-framework might be problematic. Click here for more details.

Files changed (42) hide show
  1. mcli/lib/custom_commands.py +10 -0
  2. mcli/lib/optional_deps.py +240 -0
  3. mcli/ml/backtesting/run.py +5 -3
  4. mcli/ml/models/ensemble_models.py +1 -0
  5. mcli/ml/models/recommendation_models.py +1 -0
  6. mcli/ml/optimization/optimize.py +6 -4
  7. mcli/ml/serving/serve.py +2 -2
  8. mcli/ml/training/train.py +14 -7
  9. mcli/self/completion_cmd.py +2 -2
  10. mcli/workflow/doc_convert.py +82 -112
  11. mcli/workflow/git_commit/ai_service.py +13 -2
  12. mcli/workflow/notebook/converter.py +375 -0
  13. mcli/workflow/notebook/notebook_cmd.py +441 -0
  14. mcli/workflow/notebook/schema.py +402 -0
  15. mcli/workflow/notebook/validator.py +313 -0
  16. mcli/workflow/workflow.py +14 -0
  17. {mcli_framework-7.10.0.dist-info → mcli_framework-7.10.2.dist-info}/METADATA +37 -3
  18. {mcli_framework-7.10.0.dist-info → mcli_framework-7.10.2.dist-info}/RECORD +22 -37
  19. mcli/ml/features/political_features.py +0 -677
  20. mcli/ml/preprocessing/politician_trading_preprocessor.py +0 -570
  21. mcli/workflow/politician_trading/config.py +0 -134
  22. mcli/workflow/politician_trading/connectivity.py +0 -492
  23. mcli/workflow/politician_trading/data_sources.py +0 -654
  24. mcli/workflow/politician_trading/database.py +0 -412
  25. mcli/workflow/politician_trading/demo.py +0 -249
  26. mcli/workflow/politician_trading/models.py +0 -327
  27. mcli/workflow/politician_trading/monitoring.py +0 -413
  28. mcli/workflow/politician_trading/scrapers.py +0 -1074
  29. mcli/workflow/politician_trading/scrapers_california.py +0 -434
  30. mcli/workflow/politician_trading/scrapers_corporate_registry.py +0 -797
  31. mcli/workflow/politician_trading/scrapers_eu.py +0 -376
  32. mcli/workflow/politician_trading/scrapers_free_sources.py +0 -509
  33. mcli/workflow/politician_trading/scrapers_third_party.py +0 -373
  34. mcli/workflow/politician_trading/scrapers_uk.py +0 -378
  35. mcli/workflow/politician_trading/scrapers_us_states.py +0 -471
  36. mcli/workflow/politician_trading/seed_database.py +0 -520
  37. mcli/workflow/politician_trading/supabase_functions.py +0 -354
  38. mcli/workflow/politician_trading/workflow.py +0 -879
  39. {mcli_framework-7.10.0.dist-info → mcli_framework-7.10.2.dist-info}/WHEEL +0 -0
  40. {mcli_framework-7.10.0.dist-info → mcli_framework-7.10.2.dist-info}/entry_points.txt +0 -0
  41. {mcli_framework-7.10.0.dist-info → mcli_framework-7.10.2.dist-info}/licenses/LICENSE +0 -0
  42. {mcli_framework-7.10.0.dist-info → mcli_framework-7.10.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,375 @@
1
+ """
2
+ Converter for transforming between MCLI workflow JSON and notebook format.
3
+
4
+ This module provides bidirectional conversion between:
5
+ 1. Legacy MCLI workflow JSON format (single code field)
6
+ 2. New Jupyter-compatible notebook format (multi-cell)
7
+ """
8
+
9
+ import json
10
+ import re
11
+ from datetime import datetime
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional, Union
14
+
15
+ from mcli.lib.logger.logger import get_logger
16
+
17
+ from .schema import (
18
+ CellLanguage,
19
+ CellType,
20
+ MCLIMetadata,
21
+ NotebookCell,
22
+ NotebookMetadata,
23
+ WorkflowNotebook,
24
+ )
25
+
26
+ logger = get_logger()
27
+
28
+
29
+ class WorkflowConverter:
30
+ """Convert between workflow JSON and notebook formats."""
31
+
32
+ @staticmethod
33
+ def _split_code_into_cells(code: str, language: str = "python") -> List[NotebookCell]:
34
+ """
35
+ Split a monolithic code block into logical cells.
36
+
37
+ This attempts to intelligently split code based on:
38
+ - Comment markers like # %% or # CELL
39
+ - Function/class definitions
40
+ - Major logical blocks
41
+ """
42
+ cells = []
43
+
44
+ # First, try to split by cell markers (VSCode/Jupyter style)
45
+ cell_marker_pattern = r"^#\s*%%|^#\s*<cell>|^#\s*CELL"
46
+ segments = re.split(cell_marker_pattern, code, flags=re.MULTILINE)
47
+
48
+ if len(segments) > 1:
49
+ # Found cell markers
50
+ for i, segment in enumerate(segments):
51
+ if segment.strip():
52
+ cells.append(
53
+ NotebookCell(
54
+ cell_type=CellType.CODE,
55
+ source=segment.strip() + "\n",
56
+ metadata={"language": language},
57
+ )
58
+ )
59
+ else:
60
+ # No cell markers, try to split intelligently by blank lines or major blocks
61
+ lines = code.split("\n")
62
+ current_cell_lines = []
63
+
64
+ for i, line in enumerate(lines):
65
+ current_cell_lines.append(line)
66
+
67
+ # Split on double blank lines or before major definitions
68
+ next_line = lines[i + 1] if i + 1 < len(lines) else ""
69
+ is_double_blank = line.strip() == "" and next_line.strip() == ""
70
+ is_major_def = (
71
+ next_line.strip().startswith("def ")
72
+ or next_line.strip().startswith("class ")
73
+ or next_line.strip().startswith("@")
74
+ )
75
+
76
+ if (is_double_blank or is_major_def) and len(current_cell_lines) > 3:
77
+ cell_code = "\n".join(current_cell_lines).strip()
78
+ if cell_code:
79
+ cells.append(
80
+ NotebookCell(
81
+ cell_type=CellType.CODE,
82
+ source=cell_code + "\n",
83
+ metadata={"language": language},
84
+ )
85
+ )
86
+ current_cell_lines = []
87
+
88
+ # Add remaining lines as final cell
89
+ if current_cell_lines:
90
+ cell_code = "\n".join(current_cell_lines).strip()
91
+ if cell_code:
92
+ cells.append(
93
+ NotebookCell(
94
+ cell_type=CellType.CODE,
95
+ source=cell_code + "\n",
96
+ metadata={"language": language},
97
+ )
98
+ )
99
+
100
+ # If no cells were created, add the entire code as one cell
101
+ if not cells and code.strip():
102
+ cells.append(
103
+ NotebookCell(
104
+ cell_type=CellType.CODE,
105
+ source=code,
106
+ metadata={"language": language},
107
+ )
108
+ )
109
+
110
+ return cells
111
+
112
+ @classmethod
113
+ def workflow_to_notebook(
114
+ cls, workflow_data: Dict[str, Any], add_description: bool = True
115
+ ) -> WorkflowNotebook:
116
+ """
117
+ Convert legacy workflow JSON to notebook format.
118
+
119
+ Args:
120
+ workflow_data: Legacy workflow JSON data
121
+ add_description: Add description as markdown cell
122
+
123
+ Returns:
124
+ WorkflowNotebook instance
125
+ """
126
+ # Extract metadata
127
+ name = workflow_data.get("name", "untitled")
128
+ description = workflow_data.get("description", "")
129
+ group = workflow_data.get("group")
130
+ version = workflow_data.get("version", "1.0")
131
+ language = workflow_data.get("language", "python")
132
+ created_at = workflow_data.get("created_at")
133
+ updated_at = workflow_data.get("updated_at")
134
+ extra_metadata = workflow_data.get("metadata", {})
135
+
136
+ # Create MCLI metadata
137
+ mcli_metadata = MCLIMetadata(
138
+ name=name,
139
+ description=description,
140
+ group=group,
141
+ version=version,
142
+ language=CellLanguage(language),
143
+ created_at=created_at,
144
+ updated_at=updated_at,
145
+ extra=extra_metadata,
146
+ )
147
+
148
+ # Create notebook metadata
149
+ notebook_metadata = NotebookMetadata(mcli=mcli_metadata)
150
+
151
+ # Create notebook
152
+ notebook = WorkflowNotebook(metadata=notebook_metadata)
153
+
154
+ # Add description as markdown cell if present
155
+ if add_description and description:
156
+ notebook.add_markdown_cell(f"# {name}\n\n{description}")
157
+
158
+ # Extract and split code into cells
159
+ code = workflow_data.get("code", "")
160
+ if code:
161
+ cells = cls._split_code_into_cells(code, language)
162
+ notebook.cells.extend(cells)
163
+
164
+ return notebook
165
+
166
+ @staticmethod
167
+ def notebook_to_workflow(notebook: WorkflowNotebook) -> Dict[str, Any]:
168
+ """
169
+ Convert notebook format to legacy workflow JSON.
170
+
171
+ Args:
172
+ notebook: WorkflowNotebook instance
173
+
174
+ Returns:
175
+ Legacy workflow JSON data
176
+ """
177
+ mcli_meta = notebook.metadata.mcli
178
+
179
+ # Combine all code cells into single code field
180
+ code_parts = []
181
+ for cell in notebook.cells:
182
+ if cell.cell_type == CellType.CODE:
183
+ code_parts.append(cell.source_text)
184
+
185
+ # Join with cell markers for potential round-trip conversion
186
+ combined_code = "\n# %%\n".join(code_parts)
187
+
188
+ # Build workflow data
189
+ workflow_data = {
190
+ "name": mcli_meta.name,
191
+ "description": mcli_meta.description,
192
+ "version": mcli_meta.version,
193
+ "language": mcli_meta.language.value,
194
+ "code": combined_code,
195
+ }
196
+
197
+ # Add optional fields
198
+ if mcli_meta.group:
199
+ workflow_data["group"] = mcli_meta.group
200
+ if mcli_meta.created_at:
201
+ workflow_data["created_at"] = mcli_meta.created_at
202
+ if mcli_meta.updated_at:
203
+ workflow_data["updated_at"] = mcli_meta.updated_at
204
+ else:
205
+ workflow_data["updated_at"] = datetime.utcnow().isoformat() + "Z"
206
+
207
+ if mcli_meta.extra:
208
+ workflow_data["metadata"] = mcli_meta.extra
209
+
210
+ return workflow_data
211
+
212
+ @classmethod
213
+ def load_workflow_json(cls, path: Union[str, Path]) -> Dict[str, Any]:
214
+ """Load workflow JSON from file."""
215
+ path = Path(path)
216
+ with open(path, "r") as f:
217
+ return json.load(f)
218
+
219
+ @classmethod
220
+ def save_workflow_json(cls, data: Dict[str, Any], path: Union[str, Path]) -> None:
221
+ """Save workflow JSON to file."""
222
+ path = Path(path)
223
+ with open(path, "w") as f:
224
+ json.dump(data, f, indent=2)
225
+
226
+ @classmethod
227
+ def load_notebook_json(cls, path: Union[str, Path]) -> WorkflowNotebook:
228
+ """Load notebook from JSON file."""
229
+ path = Path(path)
230
+ with open(path, "r") as f:
231
+ data = json.load(f)
232
+
233
+ # Check if it's a notebook or legacy workflow format
234
+ if "nbformat" in data:
235
+ # It's already a notebook
236
+ return WorkflowNotebook.from_dict(data)
237
+ else:
238
+ # It's a legacy workflow, convert it
239
+ logger.info(f"Converting legacy workflow to notebook format: {path}")
240
+ return cls.workflow_to_notebook(data)
241
+
242
+ @classmethod
243
+ def save_notebook_json(cls, notebook: WorkflowNotebook, path: Union[str, Path]) -> None:
244
+ """Save notebook to JSON file."""
245
+ path = Path(path)
246
+ data = notebook.to_dict()
247
+ with open(path, "w") as f:
248
+ json.dump(data, f, indent=2)
249
+
250
+ @classmethod
251
+ def convert_file_to_notebook(
252
+ cls, input_path: Union[str, Path], output_path: Optional[Union[str, Path]] = None
253
+ ) -> Path:
254
+ """
255
+ Convert a workflow JSON file to notebook format.
256
+
257
+ Args:
258
+ input_path: Path to legacy workflow JSON
259
+ output_path: Optional output path (defaults to same path)
260
+
261
+ Returns:
262
+ Path to the converted notebook file
263
+ """
264
+ input_path = Path(input_path)
265
+ output_path = Path(output_path) if output_path else input_path
266
+
267
+ # Load legacy workflow
268
+ workflow_data = cls.load_workflow_json(input_path)
269
+
270
+ # Convert to notebook
271
+ notebook = cls.workflow_to_notebook(workflow_data)
272
+
273
+ # Save notebook
274
+ cls.save_notebook_json(notebook, output_path)
275
+
276
+ logger.info(f"Converted {input_path} to notebook format at {output_path}")
277
+ return output_path
278
+
279
+ @classmethod
280
+ def convert_file_to_workflow(
281
+ cls, input_path: Union[str, Path], output_path: Optional[Union[str, Path]] = None
282
+ ) -> Path:
283
+ """
284
+ Convert a notebook file to legacy workflow JSON format.
285
+
286
+ Args:
287
+ input_path: Path to notebook JSON
288
+ output_path: Optional output path (defaults to same path)
289
+
290
+ Returns:
291
+ Path to the converted workflow file
292
+ """
293
+ input_path = Path(input_path)
294
+ output_path = Path(output_path) if output_path else input_path
295
+
296
+ # Load notebook
297
+ notebook = cls.load_notebook_json(input_path)
298
+
299
+ # Convert to workflow
300
+ workflow_data = cls.notebook_to_workflow(notebook)
301
+
302
+ # Save workflow
303
+ cls.save_workflow_json(workflow_data, output_path)
304
+
305
+ logger.info(f"Converted {input_path} to workflow format at {output_path}")
306
+ return output_path
307
+
308
+ @classmethod
309
+ def migrate_directory(
310
+ cls, directory: Union[str, Path], backup: bool = True, in_place: bool = True
311
+ ) -> Dict[str, Any]:
312
+ """
313
+ Migrate all workflow JSON files in a directory to notebook format.
314
+
315
+ Args:
316
+ directory: Directory containing workflow JSON files
317
+ backup: Create backup files before conversion
318
+ in_place: Convert files in place (vs creating new files)
319
+
320
+ Returns:
321
+ Dictionary with migration results
322
+ """
323
+ directory = Path(directory)
324
+ results = {
325
+ "total": 0,
326
+ "converted": 0,
327
+ "failed": 0,
328
+ "skipped": 0,
329
+ "files": [],
330
+ }
331
+
332
+ for json_file in directory.glob("*.json"):
333
+ # Skip lockfile and already-converted notebooks
334
+ if json_file.name == "commands.lock.json":
335
+ continue
336
+
337
+ try:
338
+ # Load and check if already a notebook
339
+ with open(json_file, "r") as f:
340
+ data = json.load(f)
341
+
342
+ results["total"] += 1
343
+
344
+ if "nbformat" in data:
345
+ # Already a notebook
346
+ results["skipped"] += 1
347
+ logger.debug(f"Skipping {json_file.name} - already a notebook")
348
+ continue
349
+
350
+ # Backup if requested
351
+ if backup:
352
+ backup_path = json_file.with_suffix(".json.bak")
353
+ cls.save_workflow_json(data, backup_path)
354
+ logger.debug(f"Created backup: {backup_path}")
355
+
356
+ # Convert to notebook
357
+ if in_place:
358
+ output_path = json_file
359
+ else:
360
+ output_path = json_file.with_stem(f"{json_file.stem}.notebook")
361
+
362
+ cls.convert_file_to_notebook(json_file, output_path)
363
+
364
+ results["converted"] += 1
365
+ results["files"].append(str(json_file))
366
+
367
+ except Exception as e:
368
+ logger.error(f"Failed to convert {json_file}: {e}")
369
+ results["failed"] += 1
370
+
371
+ logger.info(
372
+ f"Migration complete: {results['converted']} converted, "
373
+ f"{results['skipped']} skipped, {results['failed']} failed"
374
+ )
375
+ return results