Coverage for archdocs/diagram_model.py: 100%
35 statements
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 00:00 +0000
« prev ^ index » next coverage.py v7.15.3, created at 2026-08-03 00:00 +0000
1import dataclasses
2import enum
3import re as py_re
4import typing
6from archdocs import settings
9_UNSAFE_NODE_ID_PATTERN: typing.Final = py_re.compile(r"[^A-Za-z0-9_]+")
12@typing.final
13class NodeShape(enum.Enum):
14 plain_node = '{defined_node_id}["{node_label}"]'
15 service_node = '{defined_node_id}{{"{node_label}"}}'
18# Declaration order is emission order, and mermaid lays a group out where it is written.
19@typing.final
20class NodeGroup(enum.Enum):
21 inbound_api = "Inbound API"
22 messaging_and_tasks = "Messaging & tasks"
23 outbound_calls = "Outbound calls"
24 data_stores = "Data stores"
25 configuration = "Configuration"
28@typing.final
29@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
30class DiagramNode:
31 defined_node_id: str
32 node_label: str
33 node_group: NodeGroup | None = None
34 node_shape: NodeShape = NodeShape.plain_node
37@typing.final
38@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
39class DiagramEdge:
40 source_node: DiagramNode
41 target_node: DiagramNode
42 edge_label: str = ""
45def render_node_id(raw_node_name: str) -> str:
46 return _UNSAFE_NODE_ID_PATTERN.sub("_", raw_node_name).strip("_")
49def build_diagram_node(raw_node_name: str, node_label: str, node_group: NodeGroup, /) -> DiagramNode:
50 return DiagramNode(defined_node_id=render_node_id(raw_node_name), node_label=node_label, node_group=node_group)
53def build_service_node(service_name: str, node_annotations: typing.Iterable[str] = ()) -> DiagramNode:
54 joined_annotations: typing.Final = ", ".join(filter(None, node_annotations))
55 return DiagramNode(
56 defined_node_id=render_node_id(service_name) or settings.FALLBACK_SERVICE_NODE_ID,
57 node_label=f"{service_name} ({joined_annotations})" if joined_annotations else service_name,
58 node_shape=NodeShape.service_node,
59 )
62EXTERNAL_CLIENT_NODE: typing.Final = DiagramNode(
63 defined_node_id=settings.EXTERNAL_CLIENT_NODE_ID,
64 node_label=settings.EXTERNAL_CLIENT_TITLE_FOR_SCHEMA,
65 node_group=NodeGroup.inbound_api,
66)
67EXTERNAL_API_NODE: typing.Final = DiagramNode(
68 defined_node_id=settings.EXTERNAL_API_NODE_ID,
69 node_label=settings.EXTERNAL_API_TITLE_FOR_SCHEMA,
70 node_group=NodeGroup.outbound_calls,
71)