-
Notifications
You must be signed in to change notification settings - Fork 29
feat(export): Add D2 class diagram output format #152
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Hi @Else00, Thanks for the very polished PR! This functionality looks great, and I'm definitely interested in getting it into erdantic. Just as a heads up—I'm pretty busy with work and travel over the next two weeks. Since adding support for a new output format and extending the API is a big deal, I want to be thoughtful and make sure the details of the proposed implementation make sense. D2 is also new to me, so I'll want to learn at least a little more about it. That's just all to say that I may be slow to review, but that doesn't mean I'm abandoning this or don't want to merge. If you don't get a review from me by July 16, please ping me here. |
Any news? |
Hi @Else00, thanks for your patience. I started reviewing this last night and am still working on it. |
erdantic/d2.py
Outdated
def _get_d2_cardinality(edge: Edge) -> str: | ||
"""Maps erdantic's cardinality and modality to D2 cardinality strings.""" | ||
is_many = edge.target_cardinality == Cardinality.MANY | ||
is_nullable = edge.target_modality == Modality.ZERO | ||
|
||
if is_many and is_nullable: | ||
return '"*"' # Zero or more | ||
elif is_many and not is_nullable: | ||
return '"1..*"' # One or more | ||
elif not is_many and is_nullable: | ||
return '"0..1"' # Zero or one | ||
else: # not is_many and not is_nullable | ||
return '"1"' # Exactly one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't match the existing cardinality and modality behavior. Please see this documentation.
This should handle all of the cases, including Cardinality.UNSPECIFIED
and Modality.UNSPECIFIED
.
Additionally, it looks like D2's arrowheads support crow's foot notation. For consistency, we should use those instead of just the triangle head.
I see that it only supports the four cases, so we can map Cardinality.MANY, Modality.UNSPECIFIED
to the cf-many
case for now.
erdantic/d2.py
Outdated
def _sanitize_name(name: str) -> str: | ||
"""Sanitizes a name for D2 syntax, quoting if it contains special characters.""" | ||
if any(c in name for c in " -.,;()[]{}<>"): | ||
return f'"{name}"' | ||
return name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this function necessary? Why not just always quote the name?
It seems like this logic isn't sufficient anyways. The D2 docs says that if your name collides with a reserved keyword, you'd need to quote it anyways, and we're not checking for any reserved keywords here.
Please note that I pushed some commits that you need to pull. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #152 +/- ##
=======================================
- Coverage 98.4% 98.3% -0.2%
=======================================
Files 21 22 +1
Lines 884 950 +66
=======================================
+ Hits 870 934 +64
- Misses 14 16 +2
🚀 New features to boost your workflow:
|
Apologies for the late follow-up. Implemented the requested changes:
Happy to tweak mapping defaults or quoting strategy if you prefer something different. |
…ng; CLI --d2; tests & assets; document lazy import
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds D2 class diagram support to erdantic, enabling users to generate diagrams using the D2 declarative diagramming language as an alternative to the default Graphviz output.
- Adds
to_d2()
method toEntityRelationshipDiagram
class for generating D2 format output - Implements CLI flag
--d2
for outputting D2 format to console - Maps erdantic's model and relationship metadata to D2's UML class shape syntax with proper field visibility
Reviewed Changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
erdantic/d2.py | New module implementing D2 rendering with helper functions for quoting, visibility, and cardinality mapping |
erdantic/core.py | Adds to_d2() method to EntityRelationshipDiagram class |
erdantic/cli.py | Adds --d2 CLI flag with callback to make output optional and mutual exclusivity with --dot |
tests/test_d2.py | Comprehensive unit tests for D2 rendering functionality |
tests/test_cli.py | CLI tests for the new --d2 flag |
tests/test_against_assets.py | Integration tests comparing D2 output against static asset files |
tests/assets/*.d2 | Static D2 output files for different model frameworks |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jayqi any news? I need to do something else? |
@jayqi I need to do something? |
Hi @Else00, sorry for the delay. I will take a look at this PR over the weekend. |
Summary
This PR introduces a new output format to
erdantic
, allowing users to generate class diagrams using the D2 declarative diagramming language.Motivation
While
erdantic
's default Graphviz output is excellent, D2 offers powerful, modern layout engines (like ELK and Dagre) that can be particularly effective for organizing large, complex data models automatically. This provides users with an alternative rendering backend that excels at creating clean, readable diagrams from complex schemas.As a text-based format, D2 diagrams are also highly portable and easy to version control alongside source code.
For more information on D2's class diagrams, see the official tour: https://d2lang.com/tour/uml-classes
Implementation
to_d2()
method has been added to theEntityRelationshipDiagram
class.--d2
/-D
CLI flag has been added to generate D2 output to stdout.erdantic
's model and relationship metadata to D2's UML class shape syntax, including field visibility based on Python naming conventions (_
for protected,__
for private).Usage and Example Output
The commands below generate a
diagram.d2
file from thepydantic
models and then render it into an SVG:Here is the resulting
diagram.svg
:Layout Engine Comparison on a Complex Diagram
To showcase the power of D2's layout engines, here are renderings of a more complex diagram using
dagre
,elk
, andtala
.1. Dagre (Default Layout)
d2 -l dagre complex.d2 d2_dagre.svg
2. ELK Layout
d2 -l elk complex.d2 d2_elk.svg
3. TALA Layout
d2 -l tala complex.d2 d2_tala.svg