mcli-framework 7.10.1__py3-none-any.whl → 7.11.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.
Potentially problematic release.
This version of mcli-framework might be problematic. Click here for more details.
- mcli/app/commands_cmd.py +150 -58
- mcli/app/main.py +21 -27
- mcli/lib/custom_commands.py +62 -12
- mcli/lib/optional_deps.py +240 -0
- mcli/lib/paths.py +129 -5
- mcli/self/migrate_cmd.py +261 -0
- mcli/self/self_cmd.py +8 -0
- mcli/workflow/git_commit/ai_service.py +13 -2
- mcli/workflow/notebook/__init__.py +16 -0
- mcli/workflow/notebook/converter.py +375 -0
- mcli/workflow/notebook/notebook_cmd.py +441 -0
- mcli/workflow/notebook/schema.py +402 -0
- mcli/workflow/notebook/validator.py +313 -0
- mcli/workflow/secrets/__init__.py +4 -0
- mcli/workflow/secrets/secrets_cmd.py +192 -0
- mcli/workflow/workflow.py +35 -5
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/METADATA +86 -55
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/RECORD +22 -34
- mcli/ml/features/political_features.py +0 -677
- mcli/ml/preprocessing/politician_trading_preprocessor.py +0 -570
- mcli/workflow/politician_trading/__init__.py +0 -4
- mcli/workflow/politician_trading/config.py +0 -134
- mcli/workflow/politician_trading/connectivity.py +0 -492
- mcli/workflow/politician_trading/data_sources.py +0 -654
- mcli/workflow/politician_trading/database.py +0 -412
- mcli/workflow/politician_trading/demo.py +0 -249
- mcli/workflow/politician_trading/models.py +0 -327
- mcli/workflow/politician_trading/monitoring.py +0 -413
- mcli/workflow/politician_trading/scrapers.py +0 -1074
- mcli/workflow/politician_trading/scrapers_california.py +0 -434
- mcli/workflow/politician_trading/scrapers_corporate_registry.py +0 -797
- mcli/workflow/politician_trading/scrapers_eu.py +0 -376
- mcli/workflow/politician_trading/scrapers_free_sources.py +0 -509
- mcli/workflow/politician_trading/scrapers_third_party.py +0 -373
- mcli/workflow/politician_trading/scrapers_uk.py +0 -378
- mcli/workflow/politician_trading/scrapers_us_states.py +0 -471
- mcli/workflow/politician_trading/seed_database.py +0 -520
- mcli/workflow/politician_trading/supabase_functions.py +0 -354
- mcli/workflow/politician_trading/workflow.py +0 -879
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/WHEEL +0 -0
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/entry_points.txt +0 -0
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/licenses/LICENSE +0 -0
- {mcli_framework-7.10.1.dist-info → mcli_framework-7.11.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCLI Workflow Notebook System
|
|
3
|
+
|
|
4
|
+
Visual editing of workflow files using Jupyter-compatible notebook format
|
|
5
|
+
with Monaco editor support.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .converter import WorkflowConverter
|
|
9
|
+
from .schema import NotebookCell, NotebookMetadata, WorkflowNotebook
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"NotebookCell",
|
|
13
|
+
"NotebookMetadata",
|
|
14
|
+
"WorkflowNotebook",
|
|
15
|
+
"WorkflowConverter",
|
|
16
|
+
]
|
|
@@ -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
|