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

examples/basic_usage.py
  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:

Execution 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

Mermaid Graph

Graphviz

Graphviz Graph

D2

D2 Graph

PlantUML

PlantUML Graph

Interactive HTML (Cytoscape.js)

You can view the interactive version of this graph here: topology_interactive.html