erdantic

repository·main·Indexed 19 days ago

https://github.com/drivendataorg/erdantic

A tool for automatically generating Entity Relationship Diagrams (ERDs) from Python data model classes. It supports Pydantic (V1 and V2), attrs, msgspec, and standard library dataclasses, using crow's foot notation to visualize compositional relationships. erdantic provides both a CLI for quick rendering and a Python API for programmatic customization, including a plugin system for extending support to new modeling frameworks.

Tokens
9.5K
Snippets
35
Records
55
Agent score
64%

What's inside erdantic

  1. Understand erdantic diagram notation

    main

    erdantic visualizes the relationships between data model classes:

    • Nodes: Represent data model classes (classes used to hold typed data).
    • Edges: Represent compositional relationships, where a field in one class references another class (the first class "contains" the second).
    • Cardinality and Modality: Represented using crow's foot notation on the edges.
  2. Implement a plugin with a predicate and field extractor

    main

    To create a plugin, you must implement two functions defined by the following protocols:

    1. Predicate function (ModelPredicate): A function that takes an object and returns True if it is a valid data model class for your plugin, and False otherwise. It should return a TypeGuard for the model class.
    2. Field extractor function (ModelFieldExtractor): A function that takes a model class and returns a sequence of FieldInfo instances. This is also the recommended place to resolve forward references (e.g., using model_rebuild for Pydantic or resolve_types for attrs).
  3. How plugins for model frameworks work

    main

    erdantic uses a plugins system to support different data modeling frameworks. To support a new framework or customize an existing one, you must implement and register a plugin.

    Built-in plugins include:

    • attrs (for attrs classes)
    • dataclasses (for standard library dataclasses)
    • pydantic (for Pydantic BaseModel)
    • pydantic_v1 (for legacy Pydantic v1.BaseModel)

    A plugin consists of two mandatory components: a predicate function and a field extractor function.

  4. Understand the erdantic data model

    main

    All information extracted from data models is stored in Pydantic models. Because this information is represented as static data, you can directly edit fields, serialize the models to JSON, or deserialize JSON back into erdantic model instances.

    Core classes in the data model:

    • EntityRelationshipDiagram: The top-level container for the entire diagram.
    • ModelInfo: Represents a single model (rendered as a node).
    • FieldInfo: Represents a single field on a model (rendered as a row in a node's table).
    • Edge: Represents the relationship between a model field and another model.
    • FullyQualifiedName: A utility model storing a precise reference to a model class as a Python object, allowing for re-importing.
  5. Modify model analysis or diagram rendering via subclassing

    main

    For major changes to how models are analyzed or how diagrams are rendered, you can subclass core erdantic classes.

    Warning: These classes are part of the internal API and may change between versions.

    Common Customization Tasks

    • Change model-level data extraction: Override ModelInfo.from_raw_model.
    • Change field-level data extraction: Add/override a plugin's field extractor function or override FieldInfo.from_raw_type.
    • Change table structure in DOT output:
      • To change field rows: Override FieldInfo.to_dot_row.
      • To change the table label: Override ModelInfo.to_dot_label.
  6. Use custom subclasses with `EntityRelationshipDiagram`

    main

    The recommended way to use custom subclasses (like a custom ModelInfo or Edge) is to subclass EntityRelationshipDiagram and override the relevant type annotations so the diagram uses your specific classes.

    • To use custom ModelInfo: Subclass EntityRelationshipDiagram and override the type annotation for models.
    • To use custom FieldInfo: Subclass ModelInfo (to override the fields type annotation) and update the plugin's field extractor function to return your custom FieldInfo instances.
    • To use custom Edge: Subclass EntityRelationshipDiagram and override the type annotation for edges.
    from erdantic.core import EntityRelationshipDiagram
    from erdantic.examples.pydantic import Party
    
    class CustomEntityRelationshipDiagram(EntityRelationshipDiagram):
        # Override annotations here to use custom ModelInfo, FieldInfo, or Edge
        ...
    
    diagram = CustomEntityRelationshipDiagram()
    diagram.add_model(Party)
    diagram.draw("diagram.png")
  7. Customize graph appearance in erdantic

    main

    erdantic uses Graphviz for layout and rendering via PyGraphviz. When calling the draw function, you can pass keyword arguments to override default Graphviz attributes for the graph, nodes, and edges. These values are merged with erdantic's defaults.

    Available keyword arguments:

    • graph_attr: dict[str, Any] — Key-value pairs for graph attributes (e.g., spacing).
    • node_attr: dict[str, Any] — Key-value pairs for node attributes (e.g., font size).
    • edge_attr: dict[str, Any] — Key-value pairs for edge attributes.

    Commonly used attributes:

    • graph_attr["nodesep"]: Controls vertical spacing between models.
    • graph_attr["ranksep"]: Controls horizontal spacing between models.
    • node_attr["fontsize"]: Controls font size of text in model tables.
    # Example of passing attribute overrides to draw
    draw(
        diagram,
        graph_attr={"nodesep": 0.5, "ranksep": 1.0},
        node_attr={"fontsize": 12}
    )
  8. View erdantic CLI help documentation

    main

    To see the available commands, options, and usage instructions for the erdantic command-line interface, run the help command using either the erdantic binary or via the Python module interface.

    erdantic --help
    # or
    python -m erdantic --help
  9. Install erdantic

    main

    erdantic requires pygraphviz and the Graphviz C library.

    Using Conda (Recommended for Linux/macOS): The easiest way to install all dependencies (including Graphviz) is via conda-forge:

    conda install erdantic -c conda-forge

    Using Pip: If not using conda, you must install the Graphviz C library on your system first. Once Graphviz is installed, you can install erdantic via PyPI:

    pip install erdantic

    Installing the Development Version: To install directly from the GitHub repository:

    pip install "erdantic @ git+https://github.com/drivendataorg/erdantic.git"
    conda install erdantic -c conda-forge
  10. Specify terminal models to limit diagram scope

    main

    If your composition graph is too large, you can specify 'terminal models' to stop the traversal at specific classes. This effectively 'chops up' the graph.

    Via CLI: Use the -t flag followed by the dotted path of the model to be treated as a terminus. Use multiple -t flags for multiple terminal nodes.

    Via Python: Pass a list of model classes to the terminal_models keyword argument in erd.create().

    # CLI Example
    # erdantic erdantic.examples.attrs.Party -t erdantic.examples.attrs.Quest -o party.png
    
    # Python Example
    from erdantic.examples.attrs import Party, Quest
    import erdantic as erd
    
    diagram = erd.create(Party, terminal_models=[Quest])
  11. Understand Edge, Cardinality, and Modality

    main

    Relationships between models are represented as Edge objects. An edge tracks the source model, the source field, and the target model, along with the relationship's cardinality and modality.

    • Cardinality (Maximum associations):

      • Cardinality.ONE: One instance.
      • Cardinality.MANY: Multiple instances (e.g., a collection).
      • Cardinality.UNSPECIFIED: Not explicitly known.
    • Modality (Minimum associations):

      • Modality.ZERO: Optional (nullable).
      • Modality.ONE: Required.
      • Modality.UNSPECIFIED: Not explicitly known.

    Erdantic automatically calculates these based on whether a field is a collection or a nullable type.

  12. Limit diagram scope using terminal models

    main

    If you have an enormous composition graph, you can prevent erdantic from walking the entire tree by specifying certain models as terminal nodes. These models will act as leaf nodes in the diagram.

    Using the CLI

    Use the -t flag followed by the dotted path of the model to be treated as a terminus. You can use multiple -t flags to specify multiple terminal models.

    Using the Python library

    Pass a list of model classes to the terminal_models keyword argument in erd.create().

    # CLI: Terminate the graph at the 'Quest' class
    # Use repeated -t flags for multiple terminal models
    erdantic erdantic.examples.msgspec.Party -t erdantic.examples.msgspec.Quest -o party.png
    # Python: Terminate the graph at the 'Quest' class
    from erdantic.examples.msgspec import Party, Quest
    import erdantic as erd
    
    diagram = erd.create(Party, terminal_models=[Quest])