Coverage for archdocs/diagram_model.py: 100%
35 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-18 22:03 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-18 22:03 +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# How a shape is spelled in mermaid is `mermaid_syntax`'s knowledge: the model only names them.
13@typing.final
14class NodeShape(enum.Enum):
15 plain_node = enum.auto()
16 service_node = enum.auto()
19# Declaration order is emission order, and mermaid lays a group out where it is written.
20@typing.final
21class NodeGroup(enum.Enum):
22 inbound_api = "Inbound API"
23 messaging_and_tasks = "Messaging & tasks"
24 outbound_calls = "Outbound calls"
25 data_stores = "Data stores"
26 configuration = "Configuration"
29@typing.final
30@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
31class DiagramNode:
32 defined_node_id: str
33 node_label: str
34 node_group: NodeGroup | None = None
35 node_shape: NodeShape = NodeShape.plain_node
38@typing.final
39@dataclasses.dataclass(frozen=True, slots=True, kw_only=True)
40class DiagramEdge:
41 source_node: DiagramNode
42 target_node: DiagramNode
43 edge_label: str = ""
46def render_node_id(raw_node_name: str) -> str:
47 return _UNSAFE_NODE_ID_PATTERN.sub("_", raw_node_name).strip("_")
50def build_diagram_node(raw_node_name: str, node_label: str, node_group: NodeGroup, /) -> DiagramNode:
51 return DiagramNode(defined_node_id=render_node_id(raw_node_name), node_label=node_label, node_group=node_group)
54def build_service_node(service_name: str, node_annotations: typing.Iterable[str] = ()) -> DiagramNode:
55 joined_annotations: typing.Final = ", ".join(filter(None, node_annotations))
56 return DiagramNode(
57 defined_node_id=render_node_id(service_name) or settings.FALLBACK_SERVICE_NODE_ID,
58 node_label=f"{service_name} ({joined_annotations})" if joined_annotations else service_name,
59 node_shape=NodeShape.service_node,
60 )
63EXTERNAL_CLIENT_NODE: typing.Final = DiagramNode(
64 defined_node_id=settings.EXTERNAL_CLIENT_NODE_ID,
65 node_label=settings.EXTERNAL_CLIENT_TITLE_FOR_SCHEMA,
66 node_group=NodeGroup.inbound_api,
67)
68EXTERNAL_API_NODE: typing.Final = DiagramNode(
69 defined_node_id=settings.EXTERNAL_API_NODE_ID,
70 node_label=settings.EXTERNAL_API_TITLE_FOR_SCHEMA,
71 node_group=NodeGroup.outbound_calls,
72)