mcli-framework 7.0.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.

Files changed (186) hide show
  1. mcli/app/chat_cmd.py +42 -0
  2. mcli/app/commands_cmd.py +226 -0
  3. mcli/app/completion_cmd.py +216 -0
  4. mcli/app/completion_helpers.py +288 -0
  5. mcli/app/cron_test_cmd.py +697 -0
  6. mcli/app/logs_cmd.py +419 -0
  7. mcli/app/main.py +492 -0
  8. mcli/app/model/model.py +1060 -0
  9. mcli/app/model_cmd.py +227 -0
  10. mcli/app/redis_cmd.py +269 -0
  11. mcli/app/video/video.py +1114 -0
  12. mcli/app/visual_cmd.py +303 -0
  13. mcli/chat/chat.py +2409 -0
  14. mcli/chat/command_rag.py +514 -0
  15. mcli/chat/enhanced_chat.py +652 -0
  16. mcli/chat/system_controller.py +1010 -0
  17. mcli/chat/system_integration.py +1016 -0
  18. mcli/cli.py +25 -0
  19. mcli/config.toml +20 -0
  20. mcli/lib/api/api.py +586 -0
  21. mcli/lib/api/daemon_client.py +203 -0
  22. mcli/lib/api/daemon_client_local.py +44 -0
  23. mcli/lib/api/daemon_decorator.py +217 -0
  24. mcli/lib/api/mcli_decorators.py +1032 -0
  25. mcli/lib/auth/auth.py +85 -0
  26. mcli/lib/auth/aws_manager.py +85 -0
  27. mcli/lib/auth/azure_manager.py +91 -0
  28. mcli/lib/auth/credential_manager.py +192 -0
  29. mcli/lib/auth/gcp_manager.py +93 -0
  30. mcli/lib/auth/key_manager.py +117 -0
  31. mcli/lib/auth/mcli_manager.py +93 -0
  32. mcli/lib/auth/token_manager.py +75 -0
  33. mcli/lib/auth/token_util.py +1011 -0
  34. mcli/lib/config/config.py +47 -0
  35. mcli/lib/discovery/__init__.py +1 -0
  36. mcli/lib/discovery/command_discovery.py +274 -0
  37. mcli/lib/erd/erd.py +1345 -0
  38. mcli/lib/erd/generate_graph.py +453 -0
  39. mcli/lib/files/files.py +76 -0
  40. mcli/lib/fs/fs.py +109 -0
  41. mcli/lib/lib.py +29 -0
  42. mcli/lib/logger/logger.py +611 -0
  43. mcli/lib/performance/optimizer.py +409 -0
  44. mcli/lib/performance/rust_bridge.py +502 -0
  45. mcli/lib/performance/uvloop_config.py +154 -0
  46. mcli/lib/pickles/pickles.py +50 -0
  47. mcli/lib/search/cached_vectorizer.py +479 -0
  48. mcli/lib/services/data_pipeline.py +460 -0
  49. mcli/lib/services/lsh_client.py +441 -0
  50. mcli/lib/services/redis_service.py +387 -0
  51. mcli/lib/shell/shell.py +137 -0
  52. mcli/lib/toml/toml.py +33 -0
  53. mcli/lib/ui/styling.py +47 -0
  54. mcli/lib/ui/visual_effects.py +634 -0
  55. mcli/lib/watcher/watcher.py +185 -0
  56. mcli/ml/api/app.py +215 -0
  57. mcli/ml/api/middleware.py +224 -0
  58. mcli/ml/api/routers/admin_router.py +12 -0
  59. mcli/ml/api/routers/auth_router.py +244 -0
  60. mcli/ml/api/routers/backtest_router.py +12 -0
  61. mcli/ml/api/routers/data_router.py +12 -0
  62. mcli/ml/api/routers/model_router.py +302 -0
  63. mcli/ml/api/routers/monitoring_router.py +12 -0
  64. mcli/ml/api/routers/portfolio_router.py +12 -0
  65. mcli/ml/api/routers/prediction_router.py +267 -0
  66. mcli/ml/api/routers/trade_router.py +12 -0
  67. mcli/ml/api/routers/websocket_router.py +76 -0
  68. mcli/ml/api/schemas.py +64 -0
  69. mcli/ml/auth/auth_manager.py +425 -0
  70. mcli/ml/auth/models.py +154 -0
  71. mcli/ml/auth/permissions.py +302 -0
  72. mcli/ml/backtesting/backtest_engine.py +502 -0
  73. mcli/ml/backtesting/performance_metrics.py +393 -0
  74. mcli/ml/cache.py +400 -0
  75. mcli/ml/cli/main.py +398 -0
  76. mcli/ml/config/settings.py +394 -0
  77. mcli/ml/configs/dvc_config.py +230 -0
  78. mcli/ml/configs/mlflow_config.py +131 -0
  79. mcli/ml/configs/mlops_manager.py +293 -0
  80. mcli/ml/dashboard/app.py +532 -0
  81. mcli/ml/dashboard/app_integrated.py +738 -0
  82. mcli/ml/dashboard/app_supabase.py +560 -0
  83. mcli/ml/dashboard/app_training.py +615 -0
  84. mcli/ml/dashboard/cli.py +51 -0
  85. mcli/ml/data_ingestion/api_connectors.py +501 -0
  86. mcli/ml/data_ingestion/data_pipeline.py +567 -0
  87. mcli/ml/data_ingestion/stream_processor.py +512 -0
  88. mcli/ml/database/migrations/env.py +94 -0
  89. mcli/ml/database/models.py +667 -0
  90. mcli/ml/database/session.py +200 -0
  91. mcli/ml/experimentation/ab_testing.py +845 -0
  92. mcli/ml/features/ensemble_features.py +607 -0
  93. mcli/ml/features/political_features.py +676 -0
  94. mcli/ml/features/recommendation_engine.py +809 -0
  95. mcli/ml/features/stock_features.py +573 -0
  96. mcli/ml/features/test_feature_engineering.py +346 -0
  97. mcli/ml/logging.py +85 -0
  98. mcli/ml/mlops/data_versioning.py +518 -0
  99. mcli/ml/mlops/experiment_tracker.py +377 -0
  100. mcli/ml/mlops/model_serving.py +481 -0
  101. mcli/ml/mlops/pipeline_orchestrator.py +614 -0
  102. mcli/ml/models/base_models.py +324 -0
  103. mcli/ml/models/ensemble_models.py +675 -0
  104. mcli/ml/models/recommendation_models.py +474 -0
  105. mcli/ml/models/test_models.py +487 -0
  106. mcli/ml/monitoring/drift_detection.py +676 -0
  107. mcli/ml/monitoring/metrics.py +45 -0
  108. mcli/ml/optimization/portfolio_optimizer.py +834 -0
  109. mcli/ml/preprocessing/data_cleaners.py +451 -0
  110. mcli/ml/preprocessing/feature_extractors.py +491 -0
  111. mcli/ml/preprocessing/ml_pipeline.py +382 -0
  112. mcli/ml/preprocessing/politician_trading_preprocessor.py +569 -0
  113. mcli/ml/preprocessing/test_preprocessing.py +294 -0
  114. mcli/ml/scripts/populate_sample_data.py +200 -0
  115. mcli/ml/tasks.py +400 -0
  116. mcli/ml/tests/test_integration.py +429 -0
  117. mcli/ml/tests/test_training_dashboard.py +387 -0
  118. mcli/public/oi/oi.py +15 -0
  119. mcli/public/public.py +4 -0
  120. mcli/self/self_cmd.py +1246 -0
  121. mcli/workflow/daemon/api_daemon.py +800 -0
  122. mcli/workflow/daemon/async_command_database.py +681 -0
  123. mcli/workflow/daemon/async_process_manager.py +591 -0
  124. mcli/workflow/daemon/client.py +530 -0
  125. mcli/workflow/daemon/commands.py +1196 -0
  126. mcli/workflow/daemon/daemon.py +905 -0
  127. mcli/workflow/daemon/daemon_api.py +59 -0
  128. mcli/workflow/daemon/enhanced_daemon.py +571 -0
  129. mcli/workflow/daemon/process_cli.py +244 -0
  130. mcli/workflow/daemon/process_manager.py +439 -0
  131. mcli/workflow/daemon/test_daemon.py +275 -0
  132. mcli/workflow/dashboard/dashboard_cmd.py +113 -0
  133. mcli/workflow/docker/docker.py +0 -0
  134. mcli/workflow/file/file.py +100 -0
  135. mcli/workflow/gcloud/config.toml +21 -0
  136. mcli/workflow/gcloud/gcloud.py +58 -0
  137. mcli/workflow/git_commit/ai_service.py +328 -0
  138. mcli/workflow/git_commit/commands.py +430 -0
  139. mcli/workflow/lsh_integration.py +355 -0
  140. mcli/workflow/model_service/client.py +594 -0
  141. mcli/workflow/model_service/download_and_run_efficient_models.py +288 -0
  142. mcli/workflow/model_service/lightweight_embedder.py +397 -0
  143. mcli/workflow/model_service/lightweight_model_server.py +714 -0
  144. mcli/workflow/model_service/lightweight_test.py +241 -0
  145. mcli/workflow/model_service/model_service.py +1955 -0
  146. mcli/workflow/model_service/ollama_efficient_runner.py +425 -0
  147. mcli/workflow/model_service/pdf_processor.py +386 -0
  148. mcli/workflow/model_service/test_efficient_runner.py +234 -0
  149. mcli/workflow/model_service/test_example.py +315 -0
  150. mcli/workflow/model_service/test_integration.py +131 -0
  151. mcli/workflow/model_service/test_new_features.py +149 -0
  152. mcli/workflow/openai/openai.py +99 -0
  153. mcli/workflow/politician_trading/commands.py +1790 -0
  154. mcli/workflow/politician_trading/config.py +134 -0
  155. mcli/workflow/politician_trading/connectivity.py +490 -0
  156. mcli/workflow/politician_trading/data_sources.py +395 -0
  157. mcli/workflow/politician_trading/database.py +410 -0
  158. mcli/workflow/politician_trading/demo.py +248 -0
  159. mcli/workflow/politician_trading/models.py +165 -0
  160. mcli/workflow/politician_trading/monitoring.py +413 -0
  161. mcli/workflow/politician_trading/scrapers.py +966 -0
  162. mcli/workflow/politician_trading/scrapers_california.py +412 -0
  163. mcli/workflow/politician_trading/scrapers_eu.py +377 -0
  164. mcli/workflow/politician_trading/scrapers_uk.py +350 -0
  165. mcli/workflow/politician_trading/scrapers_us_states.py +438 -0
  166. mcli/workflow/politician_trading/supabase_functions.py +354 -0
  167. mcli/workflow/politician_trading/workflow.py +852 -0
  168. mcli/workflow/registry/registry.py +180 -0
  169. mcli/workflow/repo/repo.py +223 -0
  170. mcli/workflow/scheduler/commands.py +493 -0
  171. mcli/workflow/scheduler/cron_parser.py +238 -0
  172. mcli/workflow/scheduler/job.py +182 -0
  173. mcli/workflow/scheduler/monitor.py +139 -0
  174. mcli/workflow/scheduler/persistence.py +324 -0
  175. mcli/workflow/scheduler/scheduler.py +679 -0
  176. mcli/workflow/sync/sync_cmd.py +437 -0
  177. mcli/workflow/sync/test_cmd.py +314 -0
  178. mcli/workflow/videos/videos.py +242 -0
  179. mcli/workflow/wakatime/wakatime.py +11 -0
  180. mcli/workflow/workflow.py +37 -0
  181. mcli_framework-7.0.0.dist-info/METADATA +479 -0
  182. mcli_framework-7.0.0.dist-info/RECORD +186 -0
  183. mcli_framework-7.0.0.dist-info/WHEEL +5 -0
  184. mcli_framework-7.0.0.dist-info/entry_points.txt +7 -0
  185. mcli_framework-7.0.0.dist-info/licenses/LICENSE +21 -0
  186. mcli_framework-7.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,453 @@
1
+ import json
2
+ import os
3
+ import time
4
+ from collections import Counter, defaultdict
5
+ from pathlib import Path
6
+
7
+ import pydot
8
+
9
+
10
+ def load_graph_data(json_file_path):
11
+ """Load the graph data from a JSON file."""
12
+ try:
13
+ with open(json_file_path, "r") as f:
14
+ return json.load(f)
15
+ except json.JSONDecodeError as e:
16
+ raise ValueError(f"Error parsing JSON file {json_file_path}: {e}")
17
+ except FileNotFoundError:
18
+ raise FileNotFoundError(f"Graph data file not found: {json_file_path}")
19
+
20
+
21
+ def build_adjacency_list(graph_data):
22
+ """Build an adjacency list from the graph data."""
23
+ try:
24
+ # Extract vertices and edges
25
+ vertices = graph_data["graph"]["m_vertices"]["value"]
26
+ edges = graph_data["graph"]["m_edges"]["value"]
27
+
28
+ # Create mapping of IDs to node info
29
+ node_map = {node["id"]: node for node in vertices}
30
+
31
+ # Build adjacency list (directed graph)
32
+ adj_list = defaultdict(list)
33
+ for edge in edges:
34
+ source = edge["source"]
35
+ target = edge["target"]
36
+ adj_list[source].append(target)
37
+
38
+ return node_map, adj_list
39
+ except KeyError as e:
40
+ raise ValueError(
41
+ f"Invalid graph data structure: missing key {e}. Check the format of your graph data file."
42
+ )
43
+ except TypeError as e:
44
+ raise ValueError(
45
+ f"Invalid graph data structure: {e}. Check the format of your graph data file."
46
+ )
47
+
48
+
49
+ def count_descendants(node_id, adj_list, visited=None):
50
+ """Count the number of descendants for a node (reachable subgraph size)."""
51
+ if visited is None:
52
+ visited = set()
53
+
54
+ if node_id in visited:
55
+ return 0
56
+
57
+ visited.add(node_id)
58
+ count = 1 # Count the node itself
59
+
60
+ for neighbor in adj_list.get(node_id, []):
61
+ if neighbor not in visited:
62
+ count += count_descendants(neighbor, adj_list, visited)
63
+
64
+ return count
65
+
66
+
67
+ def find_top_level_nodes(node_map, adj_list, top_n=10):
68
+ """Find the top N nodes with the most descendants."""
69
+ # Count descendants for each node
70
+ descendant_counts = {}
71
+ for node_id in node_map:
72
+ descendant_counts[node_id] = count_descendants(node_id, adj_list)
73
+
74
+ # Sort nodes by descendant count
75
+ sorted_nodes = sorted(descendant_counts.items(), key=lambda x: x[1], reverse=True)
76
+
77
+ # Return top N nodes
78
+ return [node_id for node_id, count in sorted_nodes[:top_n]]
79
+
80
+
81
+ def build_hierarchical_graph(top_level_nodes, node_map, adj_list, max_depth=2):
82
+ """Build a hierarchical graph with top-level nodes as roots."""
83
+ hierarchy = {}
84
+
85
+ # For each top-level node, build its subgraph
86
+ for node_id in top_level_nodes:
87
+ subgraph = {}
88
+ visited = set()
89
+ build_subgraph(node_id, node_map, adj_list, subgraph, visited, 0, max_depth)
90
+ hierarchy[node_id] = subgraph
91
+
92
+ return hierarchy
93
+
94
+
95
+ def build_subgraph(node_id, node_map, adj_list, subgraph, visited, current_depth, max_depth):
96
+ """Recursively build a subgraph for a node up to max_depth."""
97
+ if node_id in visited or current_depth > max_depth:
98
+ return
99
+
100
+ visited.add(node_id)
101
+ subgraph[node_id] = {"node_info": node_map[node_id], "children": {}}
102
+
103
+ if current_depth < max_depth:
104
+ for child_id in adj_list.get(node_id, []):
105
+ build_subgraph(
106
+ child_id,
107
+ node_map,
108
+ adj_list,
109
+ subgraph[node_id]["children"],
110
+ visited,
111
+ current_depth + 1,
112
+ max_depth,
113
+ )
114
+
115
+
116
+ def extract_fields_from_node(node_data):
117
+ """Extract fields from node data for display in the table."""
118
+ fields = []
119
+
120
+ # If this is an entity node, extract fields from the data
121
+ if node_data.get("category") == "Entity":
122
+ # Get fields from node data
123
+ if "data" in node_data:
124
+ # Add package as a field
125
+ if "package" in node_data["data"]:
126
+ fields.append(("package", node_data["data"]["package"]))
127
+
128
+ # Add name if available
129
+ if "name" in node_data["data"]:
130
+ fields.append(("name", node_data["data"]["name"]))
131
+
132
+ # Add categoryMetadataIdentifier if available
133
+ if "categoryMetadataIdentifier" in node_data["data"]:
134
+ fields.append(("type", node_data["data"]["categoryMetadataIdentifier"]))
135
+
136
+ # Add id field
137
+ if "id" in node_data:
138
+ fields.append(("id", node_data["id"]))
139
+
140
+ # Add category field
141
+ if "category" in node_data:
142
+ fields.append(("category", node_data["category"]))
143
+
144
+ return fields
145
+
146
+
147
+ def create_table_html(entity, node_data, font_size=10):
148
+ """Create HTML table-style label for a node."""
149
+ fields = extract_fields_from_node(node_data)
150
+
151
+ # Sanitize entity name
152
+ entity = entity.replace(".", "_")
153
+ entity = entity.replace("<", "[")
154
+ entity = entity.replace(">", "]")
155
+
156
+ # Start the HTML table
157
+ html = f'<<TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" CELLPADDING="2">'
158
+
159
+ # Header row
160
+ html += f'<TR><TD PORT="header" COLSPAN="2" BGCOLOR="lightgrey"><B><FONT POINT-SIZE="{font_size+2}">{entity}</FONT></B></TD></TR>'
161
+
162
+ # Add "Data" section if there are fields
163
+ if fields:
164
+ html += f'<TR><TD COLSPAN="2" BGCOLOR="#E0E0E0"><B><FONT POINT-SIZE="{font_size}">Fields</FONT></B></TD></TR>'
165
+
166
+ # Add each field
167
+ for field_name, field_value in fields:
168
+ # Convert < and > to [ and ] for HTML compatibility
169
+ if field_value:
170
+ field_value = str(field_value).replace("<", "[").replace(">", "]")
171
+ html += f'<TR><TD><FONT POINT-SIZE="{font_size}">{field_name}</FONT></TD><TD><FONT POINT-SIZE="{font_size}">{field_value}</FONT></TD></TR>'
172
+
173
+ # Close the table
174
+ html += "</TABLE>>"
175
+ return html
176
+
177
+
178
+ def create_dot_graph(hierarchy, root_node_id, max_depth=2):
179
+ """Create a DOT graph visualization from the hierarchical model."""
180
+ graph = pydot.Dot(
181
+ graph_type="digraph",
182
+ rankdir="TB",
183
+ splines="ortho",
184
+ bgcolor="white",
185
+ label=f"Hierarchical Model for {root_node_id}",
186
+ fontsize=14,
187
+ labelloc="t",
188
+ )
189
+
190
+ # Track nodes that have been added to avoid duplicates
191
+ added_nodes = set()
192
+ # Track node depths for coloring
193
+ node_depths = {root_node_id: 0}
194
+
195
+ # Add nodes and edges recursively
196
+ add_nodes_and_edges(graph, hierarchy, root_node_id, added_nodes, node_depths, max_depth)
197
+
198
+ # Create a subgraph to force the root node to be at the top
199
+ root_subgraph = pydot.Subgraph(rank="min")
200
+ root_subgraph.add_node(pydot.Node(root_node_id))
201
+ graph.add_subgraph(root_subgraph)
202
+
203
+ return graph
204
+
205
+
206
+ def add_nodes_and_edges(
207
+ graph, hierarchy, node_id, added_nodes, node_depths, max_depth, current_depth=0
208
+ ):
209
+ """Recursively add nodes and edges to the graph."""
210
+ if current_depth > max_depth or node_id in added_nodes:
211
+ return
212
+
213
+ # Find the node data
214
+ # For root nodes, it's in hierarchy[node_id][node_id]
215
+ # For other nodes, we need to look through the hierarchy to find them
216
+ if node_id in hierarchy:
217
+ node_data = hierarchy[node_id][node_id]["node_info"]
218
+ else:
219
+ # Look for this node in other nodes' children
220
+ for root in hierarchy:
221
+ found = find_node_in_hierarchy(hierarchy[root], node_id)
222
+ if found:
223
+ node_data = found
224
+ break
225
+ else:
226
+ # Node not found in hierarchy
227
+ print(f"Warning: Node {node_id} not found in hierarchy")
228
+ return
229
+
230
+ # Record node depth
231
+ node_depths[node_id] = current_depth
232
+
233
+ # Create HTML table label for this node
234
+ node_label = create_table_html(node_id, node_data)
235
+
236
+ # Determine node color based on depth
237
+ if current_depth == 0:
238
+ bg_color = "lightblue" # Root node
239
+ elif current_depth == 1:
240
+ bg_color = "#E6F5FF" # First level
241
+ else:
242
+ bg_color = "#F0F8FF" # Deeper levels
243
+
244
+ # Create the node with HTML table label
245
+ dot_node = pydot.Node(
246
+ node_id,
247
+ shape="none", # Using 'none' to allow custom HTML table
248
+ label=node_label,
249
+ style="filled",
250
+ fillcolor=bg_color,
251
+ margin="0",
252
+ )
253
+
254
+ graph.add_node(dot_node)
255
+ added_nodes.add(node_id)
256
+
257
+ # Add edges to children if not at max depth
258
+ if current_depth < max_depth:
259
+ # Get children - different path depending on whether this is a root node
260
+ if node_id in hierarchy:
261
+ children = hierarchy[node_id][node_id]["children"]
262
+ else:
263
+ # Look up this node's children
264
+ children_container = find_children_container(hierarchy, node_id)
265
+ if not children_container:
266
+ return
267
+ children = children_container
268
+
269
+ for child_id in children:
270
+ # Add an edge from this node to the child
271
+ edge = pydot.Edge(
272
+ node_id,
273
+ child_id,
274
+ dir="both",
275
+ arrowtail="none",
276
+ arrowhead="normal",
277
+ constraint=True,
278
+ color="black",
279
+ penwidth=1.5,
280
+ )
281
+ graph.add_edge(edge)
282
+
283
+ # Recursively add the child node and its children
284
+ if child_id not in added_nodes:
285
+ add_nodes_and_edges(
286
+ graph,
287
+ hierarchy,
288
+ child_id,
289
+ added_nodes,
290
+ node_depths,
291
+ max_depth,
292
+ current_depth + 1,
293
+ )
294
+
295
+
296
+ def find_node_in_hierarchy(subgraph, target_node):
297
+ """Find a node's data in the hierarchy."""
298
+ # Check each node in the subgraph
299
+ for node_id, node_data in subgraph.items():
300
+ if node_id == target_node:
301
+ return node_data["node_info"]
302
+
303
+ # Recursively check children
304
+ if "children" in node_data:
305
+ result = find_node_in_hierarchy(node_data["children"], target_node)
306
+ if result:
307
+ return result
308
+
309
+ return None
310
+
311
+
312
+ def find_children_container(hierarchy, parent_node):
313
+ """Find a node's children container in the hierarchy."""
314
+ # Check each root node
315
+ for root_id, root_data in hierarchy.items():
316
+ # Check if the target is a direct child of this root
317
+ if parent_node in root_data[root_id]["children"]:
318
+ return root_data[root_id]["children"][parent_node]["children"]
319
+
320
+ # Look in the children of this root's children
321
+ for child_id, child_data in root_data[root_id]["children"].items():
322
+ if parent_node == child_id:
323
+ return child_data["children"]
324
+
325
+ # Could add deeper searching if needed
326
+
327
+ return {}
328
+
329
+
330
+ def transform_graph(graph_data, max_depth=3, top_n=5):
331
+ """
332
+ Transform a graph into a hierarchical model based on descendant counts.
333
+
334
+ Args:
335
+ graph_data: Dictionary containing graph data with vertices and edges
336
+ max_depth: Maximum depth for building the hierarchical model
337
+ top_n: Number of top-level nodes to include
338
+
339
+ Returns:
340
+ tuple: (hierarchy, top_nodes) - Hierarchical model and list of top nodes
341
+ """
342
+ # Build adjacency list from the graph data
343
+ node_map, adj_list = build_adjacency_list(graph_data)
344
+
345
+ # Find the top N nodes based on descendant count
346
+ top_nodes = find_top_level_nodes(node_map, adj_list, top_n)
347
+
348
+ # Build hierarchical graph with these as roots
349
+ hierarchy = build_hierarchical_graph(top_nodes, node_map, adj_list, max_depth)
350
+
351
+ # Count descendants for the top nodes
352
+ top_nodes_with_counts = [(node, count_descendants(node, adj_list)) for node in top_nodes]
353
+
354
+ return hierarchy, top_nodes_with_counts
355
+
356
+
357
+ def modified_do_erd(max_depth=3, top_n=5):
358
+ """
359
+ Generate ERD diagrams based on hierarchical model where top-level nodes
360
+ are those with the largest reachable subgraphs.
361
+
362
+ Args:
363
+ max_depth: Maximum depth for the hierarchical model (default: 3)
364
+ top_n: Number of top-level nodes to include (default: 5)
365
+
366
+ Returns:
367
+ tuple: (dot_file, png_file) - Paths to the generated files for the first top node
368
+ """
369
+ from mcli.lib.logger.logger import get_logger
370
+
371
+ logger = get_logger(__name__)
372
+
373
+ try:
374
+ # Define the path to the JSON file
375
+ json_file_path = os.path.join(
376
+ os.path.dirname(
377
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
378
+ ),
379
+ "realGraph.json",
380
+ )
381
+
382
+ logger.info(f"Using graph data from: {json_file_path}")
383
+
384
+ # Validate the file exists before attempting to load it
385
+ if not os.path.exists(json_file_path):
386
+ logger.error(f"Graph data file not found: {json_file_path}")
387
+ raise FileNotFoundError(f"Graph data file not found: {json_file_path}")
388
+
389
+ # Load the graph data
390
+ graph_data = load_graph_data(json_file_path)
391
+
392
+ # Create output directory if it doesn't exist
393
+ base_dir = os.path.dirname(
394
+ os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
395
+ )
396
+ output_dir = os.path.join(base_dir, "output")
397
+ if not os.path.exists(output_dir):
398
+ os.makedirs(output_dir)
399
+ logger.info(f"Created output directory: {output_dir}")
400
+
401
+ # Transform the graph into a hierarchical model
402
+ logger.info(f"Transforming graph with max_depth={max_depth}, top_n={top_n}")
403
+ hierarchy, top_nodes = transform_graph(graph_data, max_depth, top_n)
404
+
405
+ if not top_nodes:
406
+ logger.warning("No top-level nodes found in the graph.")
407
+ return None
408
+
409
+ # Generate file paths
410
+ timestamp = str(int(time.time() * 1000000))
411
+ generated_files = []
412
+
413
+ # For each top-level node, generate DOT and PNG files
414
+ for root_node_id, descendant_count in top_nodes:
415
+ try:
416
+ # Create the DOT graph
417
+ logger.info(f"Creating DOT graph for {root_node_id}")
418
+ dot_graph = create_dot_graph(hierarchy, root_node_id, max_depth)
419
+
420
+ # Define file paths - save to output directory
421
+ depth_info = f"_depth{max_depth}"
422
+ dot_file = os.path.join(output_dir, f"{root_node_id}{depth_info}_{timestamp}.dot")
423
+ png_file = os.path.join(output_dir, f"{root_node_id}{depth_info}_{timestamp}.png")
424
+
425
+ # Save the files
426
+ logger.info(f"Saving DOT file to {dot_file}")
427
+ dot_graph.write_raw(dot_file)
428
+
429
+ logger.info(f"Generating PNG file to {png_file}")
430
+ dot_graph.write_png(png_file)
431
+
432
+ # For return values, use just the filenames without full paths
433
+ dot_filename = os.path.basename(dot_file)
434
+ png_filename = os.path.basename(png_file)
435
+
436
+ generated_files.append((dot_filename, png_filename, root_node_id, descendant_count))
437
+ logger.info(
438
+ f"Generated graph for {root_node_id} with {descendant_count} descendants: {png_filename}"
439
+ )
440
+ except Exception as e:
441
+ logger.error(f"Error generating graph for node {root_node_id}: {e}")
442
+
443
+ # Return the first top-level node's file names for compatibility
444
+ if generated_files:
445
+ return generated_files[0][0], generated_files[0][1]
446
+ return None
447
+ except Exception as e:
448
+ logger.error(f"Error in modified_do_erd: {e}")
449
+ raise
450
+
451
+
452
+ if __name__ == "__main__":
453
+ modified_do_erd(max_depth=3)
@@ -0,0 +1,76 @@
1
+ import base64
2
+ import hashlib
3
+ import os
4
+
5
+ from mcli.lib.logger.logger import get_logger
6
+
7
+ logger = get_logger(__name__)
8
+
9
+ IN_MEMORY_FILE_FINGERPRINTS = {}
10
+ NO_CHANGE_TO_FILE = -1
11
+
12
+
13
+ def encode_content(path):
14
+ """Encode file content and track changes using MD5 fingerprints"""
15
+ logger.debug(f"encode_content: {path}")
16
+
17
+ try:
18
+ with open(path, "rb") as file:
19
+ content = file.read()
20
+
21
+ # Calculate MD5 fingerprint
22
+ fingerprint = hashlib.md5(content).hexdigest()
23
+
24
+ # Check if file has changed
25
+ if IN_MEMORY_FILE_FINGERPRINTS.get(path) != fingerprint:
26
+ IN_MEMORY_FILE_FINGERPRINTS[path] = fingerprint
27
+ return base64.b64encode(content).decode("utf-8")
28
+ else:
29
+ return NO_CHANGE_TO_FILE
30
+
31
+ except Exception as e:
32
+ logger.error(f"Error encoding content from {path}: {e}")
33
+ return NO_CHANGE_TO_FILE
34
+
35
+
36
+ def merge_txt_files(folder_path, file_type=".mcli", output_file="merged_output.txt"):
37
+ """
38
+ Recursively merge .mcli files from the given folder and its subdirectories.
39
+
40
+ Args:
41
+ folder_path (str): Path to the folder to start merging from
42
+ output_file (str, optional): Name of the output merged file. Defaults to 'merged_output.txt'.
43
+ """
44
+ # Collect all .mcli files recursively
45
+ mcli_files = []
46
+ for root, _, files in os.walk(folder_path):
47
+ for file in files:
48
+ if file.endswith(file_type):
49
+ mcli_files.append(os.path.join(root, file))
50
+
51
+ # Sort files to ensure consistent ordering
52
+ mcli_files.sort()
53
+
54
+ # Open the output file and write contents
55
+ with open(output_file, "w", encoding="utf-8") as outfile:
56
+ for filepath in mcli_files:
57
+ try:
58
+ # Add a header to identify the source file
59
+ filename = os.path.basename(filepath)
60
+ outfile.write(f"\n--- Content from {filename} ---\n")
61
+
62
+ # Read and write content from the file
63
+ with open(filepath, "r", encoding="utf-8") as infile:
64
+ outfile.write(infile.read())
65
+ outfile.write("\n") # Add a newline between files
66
+ except Exception as e:
67
+ logger.info(f"Error reading {filepath}: {str(e)}")
68
+
69
+ logger.info(f"Merged {len(mcli_files)} files into {output_file}")
70
+
71
+
72
+ if __name__ == "__main__":
73
+ # Example usage
74
+ print("File utilities module")
75
+ print("Use encode_content() to track file changes")
76
+ print("Use merge_txt_files() to merge files of a specific type")
mcli/lib/fs/fs.py ADDED
@@ -0,0 +1,109 @@
1
+ """
2
+ File system utilities for MCLI
3
+ Provides basic file system operations with path normalization
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import shutil
9
+ from pathlib import Path
10
+
11
+ import click
12
+
13
+
14
+ def get_absolute_path(pth):
15
+ """Convert path to absolute path with user expansion"""
16
+ pth = os.path.expanduser(pth)
17
+ pth = os.path.abspath(pth)
18
+ return pth
19
+
20
+
21
+ def ensure_directory_exists(dirpath):
22
+ """Create directory if it doesn't exist"""
23
+ dirpath = get_absolute_path(dirpath)
24
+ os.makedirs(dirpath, exist_ok=True)
25
+
26
+
27
+ def delete_directory(dirpath):
28
+ """Delete directory if it exists"""
29
+ dirpath = get_absolute_path(dirpath)
30
+ if os.path.exists(dirpath):
31
+ shutil.rmtree(dirpath) # Use rmtree for non-empty directories
32
+
33
+
34
+ def delete_file(filepath):
35
+ """Delete file if it exists"""
36
+ filepath = get_absolute_path(filepath)
37
+ if os.path.exists(filepath):
38
+ os.remove(filepath)
39
+
40
+
41
+ def get_user_home():
42
+ """Get user home directory"""
43
+ return os.path.expanduser("~")
44
+
45
+
46
+ def read_line_from_file(filepath):
47
+ """Read first line from file"""
48
+ filepath = get_absolute_path(filepath)
49
+ if not os.path.exists(filepath):
50
+ raise Exception("File does not exist at: " + filepath)
51
+ with open(filepath) as f:
52
+ return f.readline().strip()
53
+
54
+
55
+ def copy_file(srcpath, dstpath):
56
+ """Copy a file from source to destination"""
57
+ srcpath = get_absolute_path(srcpath)
58
+ dstpath = get_absolute_path(dstpath)
59
+ if os.path.exists(srcpath):
60
+ # Ensure destination directory exists
61
+ os.makedirs(os.path.dirname(dstpath), exist_ok=True)
62
+ shutil.copy2(srcpath, dstpath)
63
+ return True
64
+ return False
65
+
66
+
67
+ def file_exists(path):
68
+ """Check if a file exists at the given path"""
69
+ path = get_absolute_path(path)
70
+ return os.path.exists(path)
71
+
72
+
73
+ def get_file_size(path):
74
+ """Get the size of a file in bytes"""
75
+ path = get_absolute_path(path)
76
+ if file_exists(path):
77
+ return os.path.getsize(path)
78
+ return 0
79
+
80
+
81
+ def list_files(directory, pattern="*"):
82
+ """List files in a directory matching a pattern"""
83
+ import glob
84
+
85
+ directory = get_absolute_path(directory)
86
+ if os.path.exists(directory):
87
+ pattern_path = os.path.join(directory, pattern)
88
+ return glob.glob(pattern_path)
89
+ return []
90
+
91
+
92
+ # Configuration management
93
+ CONFIG_FILE = os.path.join(click.get_app_dir("mcli"), "config.json")
94
+
95
+
96
+ def save_global_value(value):
97
+ """Save a global configuration value"""
98
+ os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
99
+ with open(CONFIG_FILE, "w") as f:
100
+ json.dump({"GLOBAL_VALUE": value}, f)
101
+
102
+
103
+ def load_global_value():
104
+ """Load a global configuration value"""
105
+ try:
106
+ with open(CONFIG_FILE, "r") as f:
107
+ return json.load(f).get("GLOBAL_VALUE")
108
+ except FileNotFoundError:
109
+ return None
mcli/lib/lib.py ADDED
@@ -0,0 +1,29 @@
1
+ import importlib.util
2
+ import sys
3
+
4
+ import click
5
+
6
+
7
+ def import_public_module(module_name: str):
8
+ prefix = "mcli.public."
9
+ spec = importlib.util.find_spec(prefix + module_name)
10
+ if spec is None:
11
+ from mcli.lib.logger.logger import get_logger
12
+
13
+ logger = get_logger()
14
+ logger.error("Module is not available")
15
+ logger.error("Please install the module or consult current import statements")
16
+ sys.exit(1)
17
+
18
+ module = importlib.util.module_from_spec(spec)
19
+ spec.loader.exec_module(module)
20
+ return module
21
+
22
+
23
+ @click.group(name="lib")
24
+ def lib():
25
+ pass
26
+
27
+
28
+ if __name__ == "__main__":
29
+ lib()