Skip to content

mermaid_erd

mermaid_erd

MermaidERDiagram — ER diagram rendering using Mermaid erDiagram syntax.

Unlike MermaidDiagram (which renders flowcharts), this module renders entity-relationship diagrams with typed columns and cardinality-annotated relationships. Designed for column-level lineage visualization.

MermaidERColumn dataclass

MermaidERColumn(
    name: str,
    col_type: str = "string",
    key: str | None = None,
    comment: str | None = None,
)

A column in an ER entity.

Attributes:

Name Type Description
name str

Column name

col_type str

Mermaid ERD attribute type (string, int, timestamp, etc.)

key str | None

Optional key marker (PK, FK, UK, or combinations like "PK, FK")

comment str | None

Optional inline comment shown in the diagram (double-quoted)

to_string

to_string() -> str

Render as a Mermaid ERD attribute line (inside entity block).

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
57
58
59
60
61
62
63
64
65
66
def to_string(self) -> str:
    """Render as a Mermaid ERD attribute line (inside entity block)."""
    # Mermaid ERD attribute syntax: type name [key] ["comment"]
    parts = [f"        {self.col_type} {self.name}"]
    if self.key:
        parts.append(self.key)
    if self.comment:
        safe = self.comment.replace('"', "'")[:80]
        parts.append(f'"{safe}"')
    return " ".join(parts)

MermaidERDiagram dataclass

MermaidERDiagram(
    entities: list[MermaidEREntity] = list(),
    relationships: list[MermaidERRelationship] = list(),
    title: str | None = None,
)

Complete Mermaid ER diagram with entities and relationships.

Unlike MermaidDiagram (which renders flowcharts), this renders entity-relationship diagrams with typed columns and cardinality relationships using the Mermaid erDiagram syntax.

add_entity

add_entity(entity: MermaidEREntity) -> MermaidEREntity

Add an entity to the diagram. Returns the existing entity if found.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
208
209
210
211
212
213
214
215
216
217
def add_entity(self, entity: MermaidEREntity) -> MermaidEREntity:
    """Add an entity to the diagram. Returns the existing entity if found."""
    existing = self.find_entity(entity.id)
    if existing:
        # Merge columns from new entity into existing
        for col in entity.columns:
            existing.add_column(col)
        return existing
    self.entities.append(entity)
    return entity

add_relationship

add_relationship(rel: MermaidERRelationship) -> None

Add a relationship if not already present.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def add_relationship(self, rel: MermaidERRelationship) -> None:
    """Add a relationship if not already present."""
    if rel not in self.relationships:
        self.relationships.append(rel)
    else:
        # Merge label into existing relationship
        existing = next(
            r
            for r in self.relationships
            if r.from_entity.id == rel.from_entity.id
            and r.to_entity.id == rel.to_entity.id
        )
        if rel.label and existing.label:
            # Append new column mapping to existing label
            existing.label = f"{existing.label}, {rel.label}"
        elif rel.label:
            existing.label = rel.label

export_to_markdown

export_to_markdown(
    export_file: str | None = None,
    footer_text: str | None = None,
) -> str

Render the diagram as a Markdown document with optional file export.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
def export_to_markdown(
    self,
    export_file: str | None = None,
    footer_text: str | None = None,
) -> str:
    """Render the diagram as a Markdown document with optional file export."""
    lines = []

    if self.title:
        lines.append(f"# {self.title}")
        lines.append("")

    lines.append(f"**Entities**: {len(self.entities)}")
    lines.append(f"**Relationships**: {len(self.relationships)}")
    lines.append("")

    lines.append("```mermaid")
    lines.append(self.to_string())
    lines.append("```")

    if footer_text:
        lines.append("")
        lines.append(footer_text.strip())

    markdown_content = "\n".join(lines)

    if export_file:
        output_path = Path(export_file)
        output_path.write_text(markdown_content, encoding="utf-8")

    return markdown_content

find_entity

find_entity(entity_id: str) -> MermaidEREntity | None

Find an entity by its ID.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
204
205
206
def find_entity(self, entity_id: str) -> MermaidEREntity | None:
    """Find an entity by its ID."""
    return next((e for e in self.entities if e.id == entity_id), None)

to_string

to_string() -> str

Render the complete Mermaid ER diagram.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
237
238
239
240
241
242
243
244
245
246
247
248
249
def to_string(self) -> str:
    """Render the complete Mermaid ER diagram."""
    lines = ["erDiagram"]

    for entity in self.entities:
        lines.append(entity.to_string())

    if self.relationships:
        lines.append("")
        for rel in self.relationships:
            lines.append(rel.to_string())

    return "\n".join(lines)

MermaidEREntity dataclass

MermaidEREntity(
    id: str,
    name: str,
    columns: list[MermaidERColumn] = list(),
)

An entity in an ER diagram with typed columns.

Attributes:

Name Type Description
id str

Unique identifier (used for deduplication and relationship references)

name str

Display name (sanitized for Mermaid output)

columns list[MermaidERColumn]

List of columns belonging to this entity

mermaid_name property

mermaid_name: str

Sanitized name for Mermaid ERD output.

add_column

add_column(column: MermaidERColumn) -> None

Add a column if not already present (by name).

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
101
102
103
104
105
106
107
108
109
def add_column(self, column: MermaidERColumn) -> None:
    """Add a column if not already present (by name)."""
    if not any(c.name == column.name for c in self.columns):
        self.columns.append(column)
    else:
        # Upgrade key if the new column has a key and existing doesn't
        existing = next(c for c in self.columns if c.name == column.name)
        if column.key and not existing.key:
            existing.key = column.key

to_string

to_string() -> str

Render as a Mermaid ERD entity block.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
111
112
113
114
115
116
117
def to_string(self) -> str:
    """Render as a Mermaid ERD entity block."""
    lines = [f"    {self.mermaid_name} {{"]
    for col in self.columns:
        lines.append(col.to_string())
    lines.append("    }")
    return "\n".join(lines)

MermaidERRelationship dataclass

MermaidERRelationship(
    from_entity: MermaidEREntity,
    to_entity: MermaidEREntity,
    cardinality: str = CARDINALITY_ONE_TO_ONE,
    label: str | None = None,
    identifying: bool = True,
)

A relationship between two ER entities.

Attributes:

Name Type Description
from_entity MermaidEREntity

Source entity

to_entity MermaidEREntity

Target entity

cardinality str

Crow's-foot cardinality string (e.g., "||--|{")

label str | None

Relationship label (column mapping summary)

identifying bool

True = solid line, False = dashed line

to_string

to_string() -> str

Render as a Mermaid ERD relationship line.

Source code in src/crew_dcs/integrations/graphs/mermaid/mermaid_erd.py
175
176
177
178
179
180
181
182
183
def to_string(self) -> str:
    """Render as a Mermaid ERD relationship line."""
    from_name = self.from_entity.mermaid_name
    to_name = self.to_entity.mermaid_name
    separator = "--" if self.identifying else ".."
    # Replace the -- in cardinality with the appropriate separator
    cardinality = self.cardinality.replace("--", separator)
    label_part = f' : "{self.label}"' if self.label else ""
    return f"    {from_name} {cardinality} {to_name}{label_part}"