hammad-python 0.0.24__py3-none-any.whl → 0.0.26__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.
@@ -0,0 +1,190 @@
1
+
2
+ from typing import TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from .base import BaseGraph
6
+
7
+
8
+ def visualize_base_graph(graph: "BaseGraph", filename: str) -> None:
9
+ """Generate a visualization of the graph with clean, readable flow."""
10
+ if not graph._action_nodes or not graph._start_action_name:
11
+ raise ValueError("No actions defined in graph")
12
+
13
+ # Build our own mermaid code for better control over layout
14
+ mermaid_lines = ["graph TD"] # Top-Down layout
15
+
16
+ # Track which nodes we've already added
17
+ added_nodes = set()
18
+
19
+ # Style definitions
20
+ mermaid_lines.append(" %% Styles")
21
+ mermaid_lines.append(" classDef startNode fill:#4CAF50,stroke:#333,stroke-width:2px,color:#fff")
22
+ mermaid_lines.append(" classDef endNode fill:#f44336,stroke:#333,stroke-width:2px,color:#fff")
23
+ mermaid_lines.append(" classDef defaultNode fill:#2196F3,stroke:#333,stroke-width:2px,color:#fff")
24
+ mermaid_lines.append("")
25
+
26
+ # Helper to get clean node ID
27
+ def get_node_id(action_name: str) -> str:
28
+ return action_name.replace(" ", "_").replace("-", "_")
29
+
30
+ # Helper to add a node if not already added
31
+ def add_node(action_name: str, is_start: bool = False, is_end: bool = False) -> None:
32
+ if action_name not in added_nodes:
33
+ node_id = get_node_id(action_name)
34
+ # Use the action name as the display label
35
+ display_name = action_name
36
+
37
+ if is_start:
38
+ mermaid_lines.append(f" {node_id}[{display_name}]:::startNode")
39
+ elif is_end:
40
+ mermaid_lines.append(f" {node_id}[{display_name}]:::endNode")
41
+ else:
42
+ mermaid_lines.append(f" {node_id}[{display_name}]:::defaultNode")
43
+ added_nodes.add(action_name)
44
+
45
+ # Add all nodes and connections
46
+ mermaid_lines.append(" %% Nodes and connections")
47
+
48
+ # Start with the start node
49
+ add_node(graph._start_action_name, is_start=True)
50
+
51
+ # Process all actions to find connections
52
+ for action_name in graph._action_nodes:
53
+ action_func = getattr(graph, action_name, None)
54
+ if action_func and hasattr(action_func, '_action_settings'):
55
+ settings = action_func._action_settings
56
+
57
+ # Add the node
58
+ add_node(action_name, is_end=settings.terminates)
59
+
60
+ # Add connections based on 'next' settings
61
+ if settings.next:
62
+ source_id = get_node_id(action_name)
63
+
64
+ if isinstance(settings.next, str):
65
+ # Simple string case
66
+ target_id = get_node_id(settings.next)
67
+ add_node(settings.next)
68
+ mermaid_lines.append(f" {source_id} --> {target_id}")
69
+
70
+ elif isinstance(settings.next, list):
71
+ # List case - branches to multiple nodes
72
+ for next_action in settings.next:
73
+ if isinstance(next_action, str):
74
+ target_id = get_node_id(next_action)
75
+ add_node(next_action)
76
+ mermaid_lines.append(f" {source_id} --> {target_id}")
77
+
78
+ elif hasattr(settings.next, '__class__') and settings.next.__class__.__name__ == 'SelectionStrategy':
79
+ # SelectionStrategy case
80
+ if settings.next.actions:
81
+ # Show all possible paths with a decision diamond
82
+ decision_id = f"{source_id}_decision"
83
+ mermaid_lines.append(f" {source_id} --> {decision_id}{{LLM Selection}}")
84
+
85
+ for next_action in settings.next.actions:
86
+ target_id = get_node_id(next_action)
87
+ add_node(next_action)
88
+ mermaid_lines.append(f" {decision_id} --> {target_id}")
89
+ else:
90
+ # If no specific actions, it can go to any node
91
+ # For visualization, show connections to all non-start nodes
92
+ decision_id = f"{source_id}_decision"
93
+ mermaid_lines.append(f" {source_id} --> {decision_id}{{LLM Selection}}")
94
+
95
+ for other_action in graph._action_nodes:
96
+ if other_action != action_name and other_action != graph._start_action_name:
97
+ target_id = get_node_id(other_action)
98
+ add_node(other_action)
99
+ mermaid_lines.append(f" {decision_id} -.-> {target_id}")
100
+
101
+ # If start node has no explicit next, but there are other nodes, show possible connections
102
+ start_func = getattr(graph, graph._start_action_name, None)
103
+ if start_func and hasattr(start_func, '_action_settings'):
104
+ if not start_func._action_settings.next and len(graph._action_nodes) > 1:
105
+ source_id = get_node_id(graph._start_action_name)
106
+ # Find end nodes (terminates=True) to connect to
107
+ for action_name in graph._action_nodes:
108
+ if action_name != graph._start_action_name:
109
+ action_func = getattr(graph, action_name, None)
110
+ if action_func and hasattr(action_func, '_action_settings'):
111
+ if action_func._action_settings.terminates:
112
+ target_id = get_node_id(action_name)
113
+ add_node(action_name, is_end=True)
114
+ mermaid_lines.append(f" {source_id} --> {target_id}")
115
+
116
+ # Join all lines
117
+ mermaid_code = "\n".join(mermaid_lines)
118
+
119
+ # Render the mermaid diagram and save it
120
+ try:
121
+ import subprocess
122
+ import tempfile
123
+ import os
124
+ import shutil
125
+
126
+ # Check if mmdc (mermaid CLI) is available
127
+ if shutil.which('mmdc') is None:
128
+ raise FileNotFoundError("mermaid-cli (mmdc) not found. Install with: npm install -g @mermaid-js/mermaid-cli")
129
+
130
+ # Create a temporary mermaid file
131
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.mmd', delete=False) as temp_file:
132
+ temp_file.write(mermaid_code)
133
+ temp_mmd_path = temp_file.name
134
+
135
+ try:
136
+ # Determine output format from filename extension
137
+ output_format = 'png' # default
138
+ if filename.lower().endswith('.svg'):
139
+ output_format = 'svg'
140
+ elif filename.lower().endswith('.pdf'):
141
+ output_format = 'pdf'
142
+
143
+ # Use mermaid CLI to render the diagram
144
+ cmd = ['mmdc', '-i', temp_mmd_path, '-o', filename]
145
+
146
+ # Add format flag only if not PNG (PNG is default)
147
+ if output_format != 'png':
148
+ cmd.extend(['-f', output_format])
149
+
150
+ # Add theme and background color
151
+ cmd.extend(['-t', 'default', '-b', 'transparent'])
152
+
153
+ result = subprocess.run(cmd, capture_output=True, text=True, check=True)
154
+
155
+ if result.returncode == 0:
156
+ print(f"Graph visualization saved to: {filename}")
157
+ else:
158
+ raise subprocess.CalledProcessError(result.returncode, result.args, result.stderr)
159
+
160
+ finally:
161
+ # Clean up temporary file
162
+ if os.path.exists(temp_mmd_path):
163
+ os.unlink(temp_mmd_path)
164
+
165
+ except FileNotFoundError as e:
166
+ # Provide helpful error message for missing mermaid CLI
167
+ print(f"Warning: {e}")
168
+ # Save as .mmd file instead
169
+ mmd_filename = filename.rsplit('.', 1)[0] + '.mmd'
170
+ with open(mmd_filename, "w") as f:
171
+ f.write(mermaid_code)
172
+ print(f"Mermaid code saved to: {mmd_filename}")
173
+ print("To render as PNG, install mermaid-cli: npm install -g @mermaid-js/mermaid-cli")
174
+
175
+ except subprocess.CalledProcessError as e:
176
+ # Handle mermaid CLI errors
177
+ print(f"Error rendering mermaid diagram: {e.stderr if e.stderr else str(e)}")
178
+ # Save as .mmd file as fallback
179
+ mmd_filename = filename.rsplit('.', 1)[0] + '.mmd'
180
+ with open(mmd_filename, "w") as f:
181
+ f.write(mermaid_code)
182
+ print(f"Mermaid code saved to: {mmd_filename} (rendering failed)")
183
+
184
+ except Exception as e:
185
+ # General fallback: save the mermaid code
186
+ print(f"Unexpected error: {e}")
187
+ mmd_filename = filename.rsplit('.', 1)[0] + '.mmd'
188
+ with open(mmd_filename, "w") as f:
189
+ f.write(mermaid_code)
190
+ print(f"Mermaid code saved to: {mmd_filename}")