Repository: tedivm/paracelsus

Issue:
Mermaid output: improve display of long comments
Excellent utility! I learned about it in _Terraform in Depth_ book.

We set [`Column.comment`](https://docs.sqlalchemy.org/en/20/core/metadata.html#sqlalchemy.schema.Column.params.comment) on our SQLAlchemy columns.

These get added to the mermaid flowchart by

https://github.com/tedivm/paracelsus/blob/f473a574aa8bdb0cbf2aa38aa2610f3ac66dca4c/paracelsus/transformers/mermaid.py#L42-L43

And output, when comments are long, is like:

![Image](https://github.com/user-attachments/assets/e38c3553-eb32-44e6-a19e-a3afd6c08613)

I wonder if there are easy to address the legibility and aspect ratio issue of these long comments, like:

- option to disable the comment column
- using a markdown string for comments to [enable](https://docs.mermaidchart.com/blog/posts/automatic-text-wrapping-in-flowcharts-is-here) word wrap

Hints from issue comments:
@tedivm: This is a great feature request! I'll try to get to this, but if you wanted to make an attempt at this I'm super happy to review any PRs.@dhimmel: Okay let me give a PR a try.

Some experiments first. Here is a mermaid diagram where the comment is quoted to "enclose the unicode text"

```mermaid
erDiagram
  llm_completions {
    UUID id PK "Unique identifier ."
    VARCHAR(7) status "Status of the LLM completion."
    VARCHAR finish_reason "Finish reason from the LLM completion. This is not coded as an enum, because upstream documentation is lacking and the stability of the values is unknown."
    FLOAT cost_cents "Cost of the LLM completion in cents.,nullable"
}
```

Here is a mermaid diagram where the comment is quoted and backticked [to](https://mermaid.js.org/syntax/flowchart.html#markdown-formatting) "enclose the markdown text". Now in this GitHub issue and on https://mermaid.live/, the comment renders as "Unsupported markdown: codespan".

```mermaid
erDiagram
  llm_completions {
    UUID id PK "Unique identifier ."
    VARCHAR(7) status "Status of the LLM completion."
    VARCHAR finish_reason "`Finish reason from the LLM completion. This is not coded as an enum, because upstream documentation is lacking and the stability of the values is unknown.`"
    FLOAT cost_cents "Cost of the LLM completion in cents.,nullable"
}
```

Ah looking deeper at the marmiad entityRelationshipDiagram [docs](https://mermaid.js.org/syntax/entityRelationshipDiagram.html#attribute-keys-and-comments) for "Attribute Keys and Comments":

> markdown formatting and unicode is not supported for keys

It looks like line breaks can be included as newline characters or via `<br>` and that sort of works, but not really (creates new row):

```mermaid
erDiagram
  llm_completions {
    UUID id PK "Unique identifier ."
    VARCHAR(7) status "Status of the LLM completion."
    VARCHAR finish_reason "Finish reason from the LLM completion.<br>This is not coded as an enum, because upstream<br>documentation is lacking<br>and the stability of the values is unknown."
    FLOAT cost_cents "Cost of the LLM completion in cents.,nullable"
}
```

So possibly for paracelsus the best solution would be an option to exclude comments? @tedivm what do you think about the best solutions? Then I will PR.
@dhimmel: I started thinking this is really an upstream issue where mermaid's `entityRelationshipDiagram` should do a better job displaying comments possibly with markdown or wrapping or supporting newlines in comments.

So I went with a simple `--omit-comments` flag in https://github.com/tedivm/paracelsus/pull/34

Solution patch:
diff --git a/README.md b/README.md
index da52347..da06f29 100644
--- a/README.md
+++ b/README.md
@@ -125,6 +125,10 @@ paracelsus graph example_app.models.base:Base \
   --column-sort preserve-order
 ```
 
+### Omit Comments
+
+By default, SQLAlchemy column comments are included in the generated mermaid diagrams. You can omit these comments using the `--omit-comments` flag, which [might improve](https://github.com/tedivm/paracelsus/issues/32) legibility.
+
 ### Generate Mermaid Diagrams
 
 
@@ -251,6 +255,7 @@ exclude_tables = [
   "comments"
 ]
 column_sort = "preserve-order"
+omit_comments = false
 ```
 
 ## Sponsorship
diff --git a/paracelsus/cli.py b/paracelsus/cli.py
index 554e634..eeea594 100644
--- a/paracelsus/cli.py
+++ b/paracelsus/cli.py
@@ -32,6 +32,11 @@ if "column_sort" in PYPROJECT_SETTINGS:
 else:
     SORT_DEFAULT = ColumnSorts.key_based.value
 
+if "omit_comments" in PYPROJECT_SETTINGS:
+    OMIT_COMMENTS_DEFAULT = PYPROJECT_SETTINGS["omit_comments"]
+else:
+    OMIT_COMMENTS_DEFAULT = False
+
 
 def get_base_class(base_class_path: str | None, settings: Dict[str, Any] | None) -> str:
     if base_class_path:
@@ -82,6 +87,13 @@ def graph(
             help="Specifies the method of sorting columns in diagrams.",
         ),
     ] = SORT_DEFAULT,  # type: ignore # Typer will fail to render the help message, but this code works.
+    omit_comments: Annotated[
+        bool,
+        typer.Option(
+            "--omit-comments",
+            help="Omit SQLAlchemy column comments from the diagram.",
+        ),
+    ] = OMIT_COMMENTS_DEFAULT,
 ):
     settings = get_pyproject_settings()
     base_class = get_base_class(base_class_path, settings)
@@ -98,6 +110,7 @@ def graph(
             python_dir=python_dir,
             format=format.value,
             column_sort=column_sort,
+            omit_comments=omit_comments,
         )
     )
 
@@ -166,6 +179,13 @@ def inject(
             help="Specifies the method of sorting columns in diagrams.",
         ),
     ] = SORT_DEFAULT,  # type: ignore # Typer will fail to render the help message, but this code works.
+    omit_comments: Annotated[
+        bool,
+        typer.Option(
+            "--omit-comments",
+            help="Omit SQLAlchemy column comments from the diagram.",
+        ),
+    ] = OMIT_COMMENTS_DEFAULT,
 ):
     settings = get_pyproject_settings()
     if "imports" in settings:
@@ -180,6 +200,7 @@ def inject(
         python_dir=python_dir,
         format=format.value,
         column_sort=column_sort,
+        omit_comments=omit_comments,
     )
 
     comment_format = transformers[format].comment_format  # type: ignore
diff --git a/paracelsus/graph.py b/paracelsus/graph.py
index 7031cd1..78e457f 100644
--- a/paracelsus/graph.py
+++ b/paracelsus/graph.py
@@ -26,6 +26,7 @@ def get_graph_string(
     python_dir: List[Path],
     format: str,
     column_sort: str,
+    omit_comments: bool = False,
 ) -> str:
     # Update the PYTHON_PATH to allow more module imports.
     sys.path.append(str(os.getcwd()))
@@ -61,7 +62,7 @@ def get_graph_string(
     filtered_metadata = filter_metadata(metadata=metadata, include_tables=include_tables)
 
     # Save the graph structure to string.
-    return str(transformer(filtered_metadata, column_sort))
+    return str(transformer(filtered_metadata, column_sort, omit_comments))
 
 
 def resolve_included_tables(
diff --git a/paracelsus/transformers/__init__.py b/paracelsus/transformers/__init__.py
index e69de29..8b13789 100644
--- a/paracelsus/transformers/__init__.py
+++ b/paracelsus/transformers/__init__.py
@@ -0,0 +1 @@
+
diff --git a/paracelsus/transformers/dot.py b/paracelsus/transformers/dot.py
index 879ba06..08740ed 100644
--- a/paracelsus/transformers/dot.py
+++ b/paracelsus/transformers/dot.py
@@ -13,11 +13,13 @@ class Dot:
     metadata: MetaData
     graph: pydot.Dot
     column_sort: str
+    omit_comments: bool
 
-    def __init__(self, metaclass: MetaData, column_sort: str) -> None:
+    def __init__(self, metaclass: MetaData, column_sort: str, omit_comments: bool = False) -> None:
         self.metadata = metaclass
         self.graph = pydot.Dot("database", graph_type="graph")
         self.column_sort = column_sort
+        self.omit_comments = omit_comments
 
         for table in self.metadata.tables.values():
             node = pydot.Node(name=table.name)
diff --git a/paracelsus/transformers/mermaid.py b/paracelsus/transformers/mermaid.py
index ac3a8a9..afaf986 100644
--- a/paracelsus/transformers/mermaid.py
+++ b/paracelsus/transformers/mermaid.py
@@ -11,10 +11,12 @@ class Mermaid:
     comment_format: str = "mermaid"
     metadata: MetaData
     column_sort: str
+    omit_comments: bool
 
-    def __init__(self, metaclass: MetaData, column_sort: str) -> None:
+    def __init__(self, metaclass: MetaData, column_sort: str, omit_comments: bool = False) -> None:
         self.metadata = metaclass
         self.column_sort = column_sort
+        self.omit_comments = omit_comments
 
     def _table(self, table: Table) -> str:
         output = f"  {table.name}"
@@ -39,7 +41,7 @@ class Mermaid:
         elif column.unique:
             column_str += " UK"
 
-        if column.comment:
+        if column.comment and not self.omit_comments:
             options.append(column.comment)
 
         if column.nullable:


Test patch:
diff --git a/tests/transformers/test_mermaid.py b/tests/transformers/test_mermaid.py
index db09935..eb8d3b9 100644
--- a/tests/transformers/test_mermaid.py
+++ b/tests/transformers/test_mermaid.py
@@ -12,3 +12,13 @@ def test_mermaid(metaclass):
 def test_mermaid_column_sort_preserve_order(metaclass, mermaid_full_string_preseve_column_sort):
     mermaid = Mermaid(metaclass=metaclass, column_sort="preserve-order")
     assert str(mermaid) == mermaid_full_string_preseve_column_sort
+
+
+def test_mermaid_keep_comments(metaclass):
+    mermaid = Mermaid(metaclass=metaclass, column_sort="key-based", omit_comments=False)
+    assert "True if post is published" in str(mermaid)
+
+
+def test_mermaid_omit_comments(metaclass):
+    mermaid = Mermaid(metaclass=metaclass, column_sort="key-based", omit_comments=True)
+    assert "True if post is published" not in str(mermaid)