✅ fresh
last synced 2026-06-17T22:39:35.514075+00:00 · coverage 100% (
c4-diagrams)Validation by Beadloom
doc_sync— same source assync-check.
C4 Diagrams
Auto-generate C4 architecture diagrams from the Beadloom architecture graph.
Source: src/beadloom/graph/c4.py
Specification
Purpose
Map the internal architecture graph (nodes, edges, part_of hierarchy) to the C4 model (System / Container / Component) and render it as Mermaid C4 or C4-PlantUML syntax. Supports three diagram levels (context, container, component) and correctly handles external systems and databases.
Entry Point
def map_to_c4(conn: sqlite3.Connection) -> tuple[list[C4Node], list[C4Relationship]]| Parameter | Type | Description |
|---|---|---|
conn | sqlite3.Connection | Database connection with nodes and edges tables. |
Returns: A tuple of (c4_nodes, c4_relationships).
Data Structures
All dataclasses are frozen (immutable).
C4Node
| Field | Type | Description |
|---|---|---|
ref_id | str | Node identifier. |
label | str | Display label (from summary, fallback to ref_id). |
c4_level | str | "System", "Container", or "Component". |
description | str | Node description (from summary). |
boundary | str | None | Parent ref_id via part_of edge (None for roots). |
is_external | bool | True if node has external tag. |
is_database | bool | True if node has database or storage tag. |
C4Relationship
| Field | Type | Description |
|---|---|---|
src | str | Source node ref_id. |
dst | str | Destination node ref_id. |
label | str | Edge kind ("uses" or "depends_on"). |
Algorithm
- Load nodes. Query all rows from
nodestable. ParseextraJSON column. Extract tags foris_external/is_databaseflag detection. - Load edges. Query all rows from
edgestable. Separatepart_ofedges (hierarchy) fromuses/depends_onedges (relationships). - Compute depth. BFS from root nodes (nodes without a
part_ofparent). Orphan nodes default to depth 0. - Assign C4 level. Priority: explicit
c4_levelin node'sextraJSON > depth heuristic (0=System, 1=Container, 2+=Component). - Build C4 nodes. For each node, create a
C4Nodewith computed level, boundary (parent viapart_of), and external/database flags. - Collect relationships. Each
uses/depends_onedge becomes aC4Relationship.
Level Filtering
def filter_c4_nodes(
nodes: list[C4Node],
relationships: list[C4Relationship],
*,
level: str = "container",
scope: str | None = None,
) -> tuple[list[C4Node], list[C4Relationship]]| Level | Behavior |
|---|---|
context | Keep only System-level nodes + external nodes. |
container | Keep System and Container nodes (default). |
component | Keep children of --scope container. Requires scope. |
Raises: ValueError if level="component" without scope, or if scope ref_id not found.
Internal Helpers
| Function | Purpose |
|---|---|
_compute_depths | BFS depth computation from roots via part_of hierarchy. |
_depth_to_c4_level | Map depth integer to C4 level string. |
_load_nodes | Load and parse all nodes from DB. |
_load_edges | Load edges, separate part_of from relationships. |
_build_c4_node | Construct a single C4Node from parsed data. |
_c4_element_name | Return C4 element name with _Ext/Db suffix. |
_sanitize_id | Replace non-alphanumeric characters with underscores. |
_filter_context | Filter nodes for context level. |
_filter_container | Filter nodes for container level. |
_filter_component | Filter nodes for component level within scope. |
Renderers
Mermaid C4
def render_c4_mermaid(
nodes: list[C4Node],
relationships: list[C4Relationship],
) -> strProduces a C4Container diagram with:
System(),Container(),Component()elements_Ext/Dbvariants for external/database nodesSystem_Boundary()for grouping children bypart_ofparent- Nested
Container_Boundary()for grandchildren Rel()for relationships — emitted only when both endpoints are declared diagram nodes. A node rendered solely as aSystem_Boundaryanchor (e.g. the System root with children) is NOT an addressable node, so aRel()to it would crash mermaid-C4'sdrawRels(Cannot read properties of undefined (reading 'x')). Such Rels are dropped (the relationship still lives in the graph + the landscape map); the drop count is logged.
Mermaid Helpers
| Function | Purpose |
|---|---|
_mermaid_node_line | Single Mermaid C4 node line. |
_mermaid_grandchildren | Nested Container_Boundary for grandchildren. |
_mermaid_top_level_node | Top-level node with optional boundary. |
_mermaid_orphan_boundaries | Boundaries for non-top-level parents. |
_declared_node_ids | The ref_ids emitted as addressable nodes (not boundaries). |
_mermaid_rel_lines | Rel() lines for declared-endpoint relationships only. |
PlantUML C4
def render_c4_plantuml(
nodes: list[C4Node],
relationships: list[C4Relationship],
) -> strProduces a complete @startuml/@enduml block with:
!includefor the C4-PlantUML stdlib- Standard macros:
System(),Container(),Component(),Rel() _Ext/Dbvariants:System_Ext(),Container_Ext(),ContainerDb(),Component_Ext(),ComponentDb()System_Boundary()for grouping
PlantUML Helpers
| Function | Purpose |
|---|---|
_node_macro | PlantUML macro call for a single node. |
_plantuml_top_level_node | Top-level node with optional boundary. |
_plantuml_orphan_boundaries | Boundaries for non-top-level parents. |
API
Public Functions
def map_to_c4(conn: sqlite3.Connection) -> tuple[list[C4Node], list[C4Relationship]]: ...
def filter_c4_nodes(nodes, relationships, *, level="container", scope=None) -> tuple[list[C4Node], list[C4Relationship]]: ...
def render_c4_mermaid(nodes: list[C4Node], relationships: list[C4Relationship]) -> str: ...
def render_c4_plantuml(nodes: list[C4Node], relationships: list[C4Relationship]) -> str: ...All functions are defined in src/beadloom/graph/c4.py.
Public Classes
@dataclass(frozen=True)
class C4Node:
ref_id: str
label: str
c4_level: str # "System" | "Container" | "Component"
description: str
boundary: str | None # part_of parent ref_id
is_external: bool
is_database: bool
@dataclass(frozen=True)
class C4Relationship:
src: str
dst: str
label: str # "uses" | "depends_on"Constants
C4_LEVELS: frozenset[str] # {"context", "container", "component"}CLI
beadloom graph [--format FORMAT] [--level LEVEL] [--scope REF_ID]| Flag | Default | Description |
|---|---|---|
--format | mermaid | Output format: mermaid, c4, c4-plantuml. |
--level | container | C4 level: context, container, component. |
--scope | (none) | Required with --level=component. Container ref_id to zoom into. |
Examples:
beadloom graph # default Mermaid flowchart
beadloom graph --format=c4 # Mermaid C4 container diagram
beadloom graph --format=c4-plantuml # PlantUML C4 diagram
beadloom graph --format=c4 --level=context # system-level overview
beadloom graph --format=c4 --level=component --scope=graph # graph internalsInvariants
C4NodeandC4Relationshipare immutable (frozen dataclasses).map_to_c4()output is sorted byref_id.- Orphan nodes (not reached by BFS) default to depth 0 (System level).
- Explicit
c4_levelin nodeextraJSON always overrides depth heuristic. _sanitize_idreplaces all non-alphanumeric characters (except underscore) with_.filter_c4_nodesraisesValueErrorforcomponentlevel withoutscope.- Renderers produce deterministic output for a given input.
Constraints
- Requires a populated SQLite database with
nodesandedgestables. - Only
usesanddepends_onedge kinds are mapped to C4 relationships. part_ofedges define hierarchy (boundary grouping), not relationships.touches_codeedges are excluded from the C4 model.- Tags must be stored as a JSON list in the node's
extracolumn under thetagskey.
Testing
Test file: tests/test_c4.py
Unit Tests
- Dataclass basics. Verify
C4NodeandC4Relationshipare frozen, fields accessible. - Empty graph.
map_to_c4()returns empty lists for an empty database. - Single root node. Verify root gets
c4_level="System", no boundary. - Depth heuristic. Verify depth 0=System, 1=Container, 2+=Component.
- Explicit override.
c4_levelinextraJSON overrides depth heuristic. - Tag detection.
external→is_external,database/storage→is_database. - Boundary grouping. Children get
boundaryset to parent ref_id. - Relationships.
uses/depends_onedges →C4Relationship,part_ofexcluded. - Labels. Label from
summary, fallback toref_id. - Orphans and cycles. Orphan nodes default to System, cycles don't crash.
Renderer Tests
- Mermaid C4. Valid
C4Containerheader,System_Boundary(),Rel(),_Ext/Dbvariants. - PlantUML C4. Valid
@startuml/@enduml,!include, macros, boundaries, variants. - Element name helper. Parametrized tests for all level × flag combinations.
Filter Tests
- Context level. Keeps System + external, filters relationships.
- Container level. Keeps System + Container, excludes Component.
- Component level. Keeps children of scope, error without scope.
- Edge cases. Empty nodes, single node, unknown scope.
CLI Integration Tests
- Format options.
--format=c4,--format=c4-plantumlproduce valid output. - Level options.
--level=context,--level=container,--level=component --scope=.... - Error paths.
--level=componentwithout--scopereturns error. - Backward compatibility.
beadloom graph(no flags) still works.