Basic Usage Example¶
The following script demonstrates the core features of graphable, including building a graph, performing topological sorts, and generating various visualizations.
This script (available in the repository as examples/basic_usage.py) shows how to:
- Define nodes and relationships.
- Use tags for organization and styling.
- Utilize orchestration attributes (duration, status).
- Perform advanced analysis (CPM, diffing, slicing).
- Generate text-based and graphical representations.
Command¶
uv run examples/basic_usage.py --mermaid-svg --graphviz-svg --output-dir docs/_static/examples > docs/_static/examples/basic_usage_output.txt
Script¶
1import argparse
2import sys
3from pathlib import Path
4
5from graphable.graph import Graph
6from graphable.graphable import Graphable
7from graphable.views.asciiflow import create_topology_ascii_flow
8from graphable.views.csv import create_topology_csv
9from graphable.views.d2 import (
10 D2StylingConfig,
11 create_topology_d2,
12 export_topology_d2_image,
13)
14from graphable.views.graphml import create_topology_graphml
15from graphable.views.graphviz import (
16 GraphvizStylingConfig,
17 create_topology_graphviz_dot,
18 export_topology_graphviz_image,
19)
20from graphable.views.html import (
21 HtmlStylingConfig,
22 create_topology_html,
23 export_topology_html,
24)
25from graphable.views.json import create_topology_json
26from graphable.views.mermaid import (
27 MermaidStylingConfig,
28 create_topology_mermaid_mmd,
29 export_topology_mermaid_image,
30)
31from graphable.views.plantuml import (
32 PlantUmlStylingConfig,
33 create_topology_plantuml,
34 export_topology_plantuml_image,
35)
36from graphable.views.texttree import create_topology_tree_txt
37from graphable.views.tikz import create_topology_tikz
38from graphable.views.toml import create_topology_toml
39from graphable.views.yaml import create_topology_yaml
40
41
42def main():
43 parser = argparse.ArgumentParser(description="Demonstrate graphable features.")
44 parser.add_argument(
45 "--d2-svg",
46 action="store_true",
47 help="Generate D2 SVG (requires d2)",
48 )
49 parser.add_argument(
50 "--graphviz-svg",
51 action="store_true",
52 help="Generate Graphviz SVG (requires dot)",
53 )
54 parser.add_argument(
55 "--interactive-html",
56 action="store_true",
57 help="Generate interactive HTML",
58 )
59 parser.add_argument(
60 "--mermaid-svg",
61 action="store_true",
62 help="Generate Mermaid SVG (requires mmdc)",
63 )
64 parser.add_argument(
65 "--output-dir",
66 type=str,
67 default="examples_output",
68 help="Directory for SVG output",
69 )
70 parser.add_argument(
71 "--puml-svg",
72 action="store_true",
73 help="Generate PlantUML SVG (requires plantuml)",
74 )
75 parser.add_argument(
76 "--png",
77 action="store_true",
78 help="Generate PNG image (auto-detects engine)",
79 )
80 args = parser.parse_args()
81
82 # 1. Define nodes and relationships
83 db = Graphable("Postgres")
84 cache = Graphable("Redis")
85 api = Graphable("FastAPI")
86 worker = Graphable("Celery")
87 ui = Graphable("React")
88
89 g = Graph()
90 g.add_edge(db, api, weight=5)
91 g.add_edge(cache, api, weight=2)
92 g.add_edge(api, ui, weight=1)
93 g.add_edge(db, worker, weight=10)
94 g.add_edge(api, worker, weight=2)
95
96 # Set durations for CPM demo
97 db.duration = 10
98 cache.duration = 2
99 api.duration = 5
100 worker.duration = 8
101 ui.duration = 3
102
103 # Add a redundant edge for transitive reduction demo
104 # Postgres -> React (redundant because Postgres -> FastAPI -> React)
105 g.add_edge(db, ui)
106
107 db.add_tag("persistence")
108 cache.add_tag("ephemeral")
109 api.add_tag("backend")
110 worker.add_tag("backend")
111 ui.add_tag("frontend")
112
113 # 2. Container Protocols & Integrity
114 print("--- 2. Container Protocols & Integrity ---")
115 print(f"Graph size: {len(g)} nodes")
116 print(f"Is 'Postgres' in graph? {'Postgres' in g}")
117 print(f"Access node by reference: {g['FastAPI'].reference}")
118
119 checksum = g.checksum()
120 print(f"Graph Checksum: {checksum}")
121 print(f"Checksum valid? {g.validate_checksum(checksum)}")
122
123 # 3. Reachability, Ordering & Parallel Sort
124 print("\n--- 3. Reachability, Ordering & Parallel Sort ---")
125 print(f"Ancestors of React: {[n.reference for n in g.ancestors(ui)]}")
126 print(f"Descendants of Postgres: {[n.reference for n in g.descendants(db)]}")
127
128 if db < ui:
129 print(f"Verified: {db.reference} is an ancestor of {ui.reference}")
130
131 print("Parallel execution layers:")
132 for i, layer in enumerate(g.parallelized_topological_order()):
133 print(f" Layer {i}: {[n.reference for n in layer]}")
134
135 # 4. Transitive Reduction
136 print("\n--- 4. Transitive Reduction ---")
137 print(f"Edges before reduction: {sum(len(n.dependents) for n in g)}")
138 reduced_g = g.transitive_reduction()
139 print(f"Edges after reduction: {sum(len(n.dependents) for n in reduced_g)}")
140
141 # 5. Advanced Analysis (v0.6.0)
142 print("\n--- 5. Advanced Analysis (v0.6.0) ---")
143
144 # CPM Analysis
145 analysis = g.cpm_analysis()
146 cp = g.critical_path()
147 print(f"Critical Path: {[n.reference for n in cp]}")
148 print(f"Project Duration: {max(v['EF'] for v in analysis.values())}")
149
150 # Slicing
151 upstream = g.upstream_of(ui)
152 print(f"Upstream of React: {[n.reference for n in upstream.topological_order()]}")
153
154 between = g.subgraph_between(db, ui)
155 print(
156 f"Between Postgres and React: {[n.reference for n in between.topological_order()]}"
157 )
158
159 # Transitive Closure
160 closure = g.transitive_closure()
161 print(f"Transitive Closure edges: {sum(len(n.dependents) for n in closure)}")
162
163 # BFS/DFS Traversals
164 print("\n--- 6. Native Traversals (BFS & DFS) ---")
165 from graphable.enums import Direction
166
167 print("BFS from Postgres (level-by-level):")
168 for node in g.bfs(db):
169 print(f" - {node.reference}")
170
171 print("DFS from React (upstream chain):")
172 for node in g.dfs(ui, direction=Direction.UP):
173 print(f" - {node.reference}")
174
175 # Diffing Demo
176 g_v2 = Graph.from_json(create_topology_json(g))
177 new_task = Graphable("Analytics")
178 g_v2.add_edge(g_v2["Postgres"], new_task)
179
180 diff_data = g.diff(g_v2)
181 print(f"Diff detected added node: {diff_data['added_nodes']}")
182
183 # 6. Basic Text Output
184 print("\n--- 6. Topological Order ---")
185 for node in g: # Using __iter__
186 tags_str = f" (Tags: {', '.join(node.tags)})" if node.tags else ""
187 print(f"- {node.reference}{tags_str}")
188
189 print("\n--- 7. Text Tree (Sinks to Sources) ---")
190 print(g.render(create_topology_tree_txt)) # Using .render()
191
192 print("\n--- 8. ASCII Flowchart ---")
193 print(create_topology_ascii_flow(g))
194
195 # 8. Visualizations with Clustering
196 print("\n--- 9. Mermaid Definition (Clustered) ---")
197 mmd_config = MermaidStylingConfig(cluster_by_tag=True)
198 print(g.render(create_topology_mermaid_mmd, config=mmd_config))
199
200 print("\n--- 9. Graphviz DOT (Clustered) ---")
201 gv_config = GraphvizStylingConfig(
202 cluster_by_tag=True,
203 graph_attr={"rankdir": "LR", "nodesep": "0.5"},
204 node_attr_default={"shape": "rounded", "style": "filled", "fontname": "Arial"},
205 )
206 print(g.render(create_topology_graphviz_dot, config=gv_config))
207
208 print("\n--- 10. D2 Definition (Clustered) ---")
209 d2_config = D2StylingConfig(
210 layout="dagre",
211 cluster_by_tag=True,
212 node_style_fnc=lambda n: {
213 "fill": "lightblue"
214 if "backend" in n.tags
215 else "lightgreen"
216 if "frontend" in n.tags
217 else "lightgrey"
218 },
219 )
220 print(create_topology_d2(g, d2_config))
221
222 # 11. Serialization formats
223 print("\n--- 11. YAML Definition ---")
224 print(create_topology_yaml(g))
225
226 print("\n--- 12. TOML Definition ---")
227 print(create_topology_toml(g))
228
229 print("\n--- 13. JSON Definition ---")
230 print(create_topology_json(g))
231
232 print("\n--- 14. CSV Edge List ---")
233 print(create_topology_csv(g))
234
235 # 15. Other Views
236 print("\n--- 15. PlantUML Definition (Clustered) ---")
237 puml_config = PlantUmlStylingConfig(
238 node_type="component",
239 direction="left to right direction",
240 cluster_by_tag=True,
241 )
242 print(create_topology_plantuml(g, puml_config))
243
244 print("\n--- 16. TikZ Definition ---")
245 print(create_topology_tikz(g))
246
247 print("\n--- 17. GraphML Definition ---")
248 print(create_topology_graphml(g))
249
250 # 18. Advanced Analysis & Interactive
251 print("\n--- 18. NetworkX Integration ---")
252 try:
253 dg = g.to_networkx()
254 print(
255 f"NetworkX DiGraph created with {dg.number_of_nodes()} nodes and {dg.number_of_edges()} edges."
256 )
257 except ImportError as e:
258 print(f"NetworkX not available: {e}")
259
260 print("\n--- 19. Interactive HTML (Snippet) ---")
261 html_config = HtmlStylingConfig(title="Demo Graph")
262 html = create_topology_html(g, html_config)
263 print(html[:200] + "...")
264
265 # 20. Mutation Demo
266 print("\n--- 20. Mutation Demo ---")
267 g.remove_edge(db, ui)
268 print(
269 f"Removed redundant edge manually. Edges: {sum(len(n.dependents) for n in g)}"
270 )
271 g.remove_node(cache)
272 print(f"Removed Redis. Graph size: {len(g)}")
273
274 # 21. Parsing Demo
275 print("\n--- 21. Parsing Demo ---")
276 json_data = create_topology_json(g)
277 parsed_g = Graph.from_json(json_data)
278 print(f"Reconstructed graph from JSON. Nodes: {len(parsed_g)}")
279 print(f"Nodes in topological order: {[n.reference for n in parsed_g]}")
280
281 # 22. Equality Demo
282 print("\n--- 22. Equality Demo ---")
283 print(f"Is parsed_g equal to g? {parsed_g == g}")
284
285 # 23. Optional SVG & HTML Generation
286 if (
287 args.mermaid_svg
288 or args.graphviz_svg
289 or args.d2_svg
290 or args.puml_svg
291 or args.interactive_html
292 ):
293 out_dir = Path(args.output_dir)
294 out_dir.mkdir(exist_ok=True)
295 print(f"\n--- Generating Assets in {out_dir}/ ---")
296
297 # Mermaid SVG
298 if args.mermaid_svg:
299 mermaid_out = out_dir / "topology_mermaid.svg"
300 try:
301 export_topology_mermaid_image(g, mermaid_out)
302 print(f"Successfully generated: {mermaid_out}")
303 except Exception as e:
304 print(f"Failed to generate Mermaid SVG: {e}", file=sys.stderr)
305
306 # Graphviz SVG
307 if args.graphviz_svg:
308 graphviz_out = out_dir / "topology_graphviz.svg"
309 try:
310 export_topology_graphviz_image(g, graphviz_out, gv_config)
311 print(f"Successfully generated: {graphviz_out}")
312 except Exception as e:
313 print(f"Failed to generate Graphviz SVG: {e}", file=sys.stderr)
314
315 # D2 SVG
316 if args.d2_svg:
317 d2_out = out_dir / "topology_d2.svg"
318 try:
319 export_topology_d2_image(g, d2_out, d2_config)
320 print(f"Successfully generated: {d2_out}")
321 except Exception as e:
322 print(f"Failed to generate D2 SVG: {e}", file=sys.stderr)
323
324 # PlantUML SVG
325 if args.puml_svg:
326 puml_out = out_dir / "topology_plantuml.svg"
327 try:
328 export_topology_plantuml_image(g, puml_out, puml_config)
329 print(f"Successfully generated: {puml_out}")
330 except Exception as e:
331 print(f"Failed to generate PlantUML SVG: {e}", file=sys.stderr)
332
333 # Interactive HTML
334 if args.interactive_html:
335 html_out = out_dir / "topology_interactive.html"
336 try:
337 export_topology_html(g, html_out, html_config)
338 print(f"Successfully generated: {html_out}")
339 except Exception as e:
340 print(f"Failed to generate Interactive HTML: {e}", file=sys.stderr)
341
342 # PNG Image (Auto-detect engine)
343 if args.png:
344 png_out = out_dir / "topology.png"
345 try:
346 # Use Graph.write which handles auto-detection and images
347 g.write(png_out)
348 print(f"Successfully generated: {png_out}")
349 except Exception as e:
350 print(f"Failed to generate PNG: {e}", file=sys.stderr)
351
352
353if __name__ == "__main__":
354 main()
Output¶
Running the script produces the following text output:
--- 2. Container Protocols & Integrity ---
Graph size: 5 nodes
Is 'Postgres' in graph? True
Access node by reference: FastAPI
Graph Checksum: 09d7131f91c8dab09bfbeff171a6fda3993147096a178d4eeaf31c6e641cbfd4ca5c4a80e177c9dd5f222695eb4460094d2c596d80817f95e8f84a0594e14208
Checksum valid? True
--- 3. Reachability, Ordering & Parallel Sort ---
Ancestors of React: ['FastAPI', 'Postgres', 'Redis']
Descendants of Postgres: ['FastAPI', 'React', 'Celery']
Verified: Postgres is an ancestor of React
Parallel execution layers:
Layer 0: ['Postgres', 'Redis']
Layer 1: ['FastAPI']
Layer 2: ['Celery', 'React']
--- 4. Transitive Reduction ---
Edges before reduction: 6
Edges after reduction: 4
--- 5. Advanced Analysis (v0.6.0) ---
Critical Path: ['Postgres', 'FastAPI', 'Celery']
Project Duration: 23.0
Upstream of React: ['Postgres', 'Redis', 'FastAPI', 'React']
Between Postgres and React: ['Postgres', 'FastAPI', 'React']
Transitive Closure edges: 8
--- 6. Native Traversals (BFS & DFS) ---
BFS from Postgres (level-by-level):
- Postgres
- FastAPI
- Celery
- React
DFS from React (upstream chain):
- React
- FastAPI
- Postgres
- Redis
Diff detected added node: {'Analytics'}
--- 6. Topological Order ---
- Postgres (Tags: persistence)
- Redis (Tags: ephemeral)
- FastAPI (Tags: backend)
- Celery (Tags: backend)
- React (Tags: frontend)
--- 7. Text Tree (Sinks to Sources) ---
Celery
├─ Postgres
└─ FastAPI
├─ Postgres
└─ Redis
React
├─ FastAPI
│ ├─ Postgres
│ └─ Redis
└─ Postgres
--- 8. ASCII Flowchart ---
+----------+
| Postgres |
+----------+
v
+--> FastAPI
+--> Celery
+--> React
+-------+
| Redis |
+-------+
v
+--> FastAPI
+---------+
| FastAPI |
+---------+
v
+--> React
+--> Celery
+--------+
| Celery |
+--------+
+-------+
| React |
+-------+
--- 9. Mermaid Definition (Clustered) ---
flowchart TD
subgraph persistence
Postgres[Postgres]
end
subgraph ephemeral
Redis[Redis]
end
subgraph backend
FastAPI[FastAPI]
Celery[Celery]
end
subgraph frontend
React[React]
end
Postgres --> FastAPI
Postgres --> Celery
Postgres --> React
Redis --> FastAPI
FastAPI --> React
FastAPI --> Celery
--- 9. Graphviz DOT (Clustered) ---
digraph G {
rankdir="LR";
nodesep="0.5";
node [shape="rounded", style="filled", fontname="Arial"];
subgraph "cluster_persistence" {
label="persistence";
"Postgres" [label="Postgres"];
}
subgraph "cluster_ephemeral" {
label="ephemeral";
"Redis" [label="Redis"];
}
subgraph "cluster_backend" {
label="backend";
"FastAPI" [label="FastAPI"];
"Celery" [label="Celery"];
}
subgraph "cluster_frontend" {
label="frontend";
"React" [label="React"];
}
"Postgres" -> "FastAPI";
"Postgres" -> "Celery";
"Postgres" -> "React";
"Redis" -> "FastAPI";
"FastAPI" -> "React";
"FastAPI" -> "Celery";
}
--- 10. D2 Definition (Clustered) ---
vars: {
d2-config: {
layout-engine: dagre
}
}
persistence: {
Postgres: Postgres
style: {
fill: lightgrey
}
}
ephemeral: {
Redis: Redis
style: {
fill: lightgrey
}
}
backend: {
FastAPI: FastAPI
style: {
fill: lightblue
}
Celery: Celery
style: {
fill: lightblue
}
}
frontend: {
React: React
style: {
fill: lightgreen
}
}
Postgres -> FastAPI
Postgres -> Celery
Postgres -> React
Redis -> FastAPI
FastAPI -> React
FastAPI -> Celery
--- 11. YAML Definition ---
nodes:
- id: Postgres
reference: Postgres
tags:
- persistence
- id: Redis
reference: Redis
tags:
- ephemeral
- id: FastAPI
reference: FastAPI
tags:
- backend
- id: Celery
reference: Celery
tags:
- backend
- id: React
reference: React
tags:
- frontend
edges:
- source: Postgres
target: FastAPI
- source: Postgres
target: Celery
- source: Postgres
target: React
- source: Redis
target: FastAPI
- source: FastAPI
target: React
- source: FastAPI
target: Celery
--- 12. TOML Definition ---
edges = [
{ source = "Postgres", target = "FastAPI" },
{ source = "Postgres", target = "Celery" },
{ source = "Postgres", target = "React" },
{ source = "Redis", target = "FastAPI" },
{ source = "FastAPI", target = "React" },
{ source = "FastAPI", target = "Celery" },
]
[[nodes]]
id = "Postgres"
reference = "Postgres"
tags = [
"persistence",
]
[[nodes]]
id = "Redis"
reference = "Redis"
tags = [
"ephemeral",
]
[[nodes]]
id = "FastAPI"
reference = "FastAPI"
tags = [
"backend",
]
[[nodes]]
id = "Celery"
reference = "Celery"
tags = [
"backend",
]
[[nodes]]
id = "React"
reference = "React"
tags = [
"frontend",
]
--- 13. JSON Definition ---
{
"nodes": [
{
"id": "Postgres",
"reference": "Postgres",
"tags": [
"persistence"
]
},
{
"id": "Redis",
"reference": "Redis",
"tags": [
"ephemeral"
]
},
{
"id": "FastAPI",
"reference": "FastAPI",
"tags": [
"backend"
]
},
{
"id": "Celery",
"reference": "Celery",
"tags": [
"backend"
]
},
{
"id": "React",
"reference": "React",
"tags": [
"frontend"
]
}
],
"edges": [
{
"source": "Postgres",
"target": "FastAPI"
},
{
"source": "Postgres",
"target": "Celery"
},
{
"source": "Postgres",
"target": "React"
},
{
"source": "Redis",
"target": "FastAPI"
},
{
"source": "FastAPI",
"target": "React"
},
{
"source": "FastAPI",
"target": "Celery"
}
]
}
--- 14. CSV Edge List ---
source,target
Postgres,FastAPI
Postgres,Celery
Postgres,React
Redis,FastAPI
FastAPI,React
FastAPI,Celery
--- 15. PlantUML Definition (Clustered) ---
@startuml
left to right direction
package "persistence" {
component "Postgres" as node_140016404397232
}
package "ephemeral" {
component "Redis" as node_140016397110160
}
package "backend" {
component "FastAPI" as node_140016397110480
component "Celery" as node_140016397207712
}
package "frontend" {
component "React" as node_140016397208016
}
node_140016404397232 --> node_140016397110480
node_140016404397232 --> node_140016397207712
node_140016404397232 --> node_140016397208016
node_140016397110160 --> node_140016397110480
node_140016397110480 --> node_140016397208016
node_140016397110480 --> node_140016397207712
@enduml
--- 16. TikZ Definition ---
\begin{tikzpicture}
\usetikzlibrary{graphs}
\graph [nodes={draw, circle}] {
node_140016404397232 ["Postgres"];
node_140016404397232 -> node_140016397110480;
node_140016404397232 -> node_140016397207712;
node_140016404397232 -> node_140016397208016;
node_140016397110160 ["Redis"];
node_140016397110160 -> node_140016397110480;
node_140016397110480 ["FastAPI"];
node_140016397110480 -> node_140016397208016;
node_140016397110480 -> node_140016397207712;
node_140016397207712 ["Celery"];
node_140016397208016 ["React"];
};
\end{tikzpicture}
--- 17. GraphML Definition ---
<?xml version="1.0" ?>
<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd">
<key id="tags" for="node" attr.name="tags" attr.type="string"/>
<graph id="G" edgedefault="directed">
<node id="Postgres">
<data key="tags">persistence</data>
</node>
<edge id="e_Postgres_FastAPI" source="Postgres" target="FastAPI"/>
<edge id="e_Postgres_Celery" source="Postgres" target="Celery"/>
<edge id="e_Postgres_React" source="Postgres" target="React"/>
<node id="Redis">
<data key="tags">ephemeral</data>
</node>
<edge id="e_Redis_FastAPI" source="Redis" target="FastAPI"/>
<node id="FastAPI">
<data key="tags">backend</data>
</node>
<edge id="e_FastAPI_React" source="FastAPI" target="React"/>
<edge id="e_FastAPI_Celery" source="FastAPI" target="Celery"/>
<node id="Celery">
<data key="tags">backend</data>
</node>
<node id="React">
<data key="tags">frontend</data>
</node>
</graph>
</graphml>
--- 18. NetworkX Integration ---
NetworkX DiGraph created with 5 nodes and 6 edges.
--- 19. Interactive HTML (Snippet) ---
<!DOCTYPE html>
<html>
<head>
<title>Demo Graph</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.28.1/cytoscape.min.js"></script>
<style>
body {
...
--- 20. Mutation Demo ---
Removed redundant edge manually. Edges: 5
Removed Redis. Graph size: 4
--- 21. Parsing Demo ---
Reconstructed graph from JSON. Nodes: 4
Nodes in topological order: ['Postgres', 'FastAPI', 'Celery', 'React']
--- 22. Equality Demo ---
Is parsed_g equal to g? False
--- Generating Assets in docs/_static/examples/ ---
Successfully generated: docs/_static/examples/topology_mermaid.svg
Successfully generated: docs/_static/examples/topology_graphviz.svg
Successfully generated: docs/_static/examples/topology_d2.svg
Successfully generated: docs/_static/examples/topology_plantuml.svg
Successfully generated: docs/_static/examples/topology_interactive.html
Visualizations¶
The following diagrams were generated by the script using the various visualization engines.
Mermaid
Graphviz
D2
PlantUML
Interactive HTML (Cytoscape.js)
You can view the interactive version of this graph here: topology_interactive.html