Extending or modifying erdantic¶
Note
The backend of erdantic was significantly updated in v1.0. This now works very differently than previous versions.
Plugins for model frameworks¶
erdantic supports data modeling frameworks through a plugins system. Each plugin must implement specific functionality and get registered for erdantic to use it.
The following built-in plugins are provided by erdantic:
- attrs— for classes decorated using the attrs package
- dataclasses— for classes decorated using the dataclasses standard library module
- pydantic— for classes that subclass Pydantic's- BaseModelclass
- pydantic_v1— for classes that subclass Pydantic's legacy- pydantic.v1.BaseModelclass
It is possible to customize erdantic by registering a custom plugin, either by overriding a provided one or adding as a new one. The following sections document what you need to register your own plugin.
Components of a plugin¶
A plugin must implement the following two components: a predicate function and a field extractor function.
Predicate function¶
A predicate function takes a single object as an input and return a boolean value True if the object is a data model class that this plugin is for and False otherwise. The return type of this function is a TypeGuard for the model class. A protocol class ModelPredicate defines the specification for a valid predicate function.
Source from erdantic/plugins/__init__.py
class ModelPredicate(Protocol[_ModelType_co]):
    """Protocol class for a predicate function for a plugin."""
    def __call__(self, obj: Any) -> TypeGuard[_ModelType_co]: ...
Example implementations of predicate functions include is_pydantic_model and is_dataclass_class.
Field extractor function¶
A field extractor function takes a single model class of the appropriate type and returns a sequence of FieldInfo instances. A protocol class ModelFieldExtractor defines the specification for a valid field extractor function.
Source from erdantic/plugins/__init__.py
class ModelFieldExtractor(Protocol[_ModelType_contra]):
    """Protocol class for a field extractor function for a plugin."""
    def __call__(self, model: _ModelType_contra) -> Sequence["FieldInfo"]: ...
Example implementations of field extractor functions include get_fields_from_pydantic_model and get_fields_from_dataclass.
The field extractor function is the place where you should try to resolve forward references. Some frameworks provide utility functions to resolve forward references, like Pydantic's model_rebuild and attr's resolve_types. If there isn't one, you should write your own using erdantic's resolve_types_on_dataclass as a reference implementation.
Registering a plugin¶
A plugin must be registered by calling the register_plugin function with a key identifier and the two functions. If you use a key that already exists, it will overwrite the existing plugin.
Info
register_plugin(
    key: str,
    predicate_fn: ModelPredicate[_ModelType],
    get_fields_fn: ModelFieldExtractor[_ModelType],
)
Register a plugin for a specific model class type.
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| key | str | An identifier for this plugin. | required | 
| predicate_fn | ModelPredicate | A predicate function to determine if an object is a class of the model that is supported by this plugin. | required | 
| get_fields_fn | ModelFieldExtractor | A function to extract fields from a model class that is supported by this plugin. | required | 
Currently, manual registration is required. This means that custom plugins can only be loaded when using erdantic as a library, and not as a CLI. In the future, we may support automatic loading of plugins that are distributed with packages through the entry points specification.
Modifying model analysis or diagram rendering¶
If you would like to make any major changes to the functionality of erdantic, such as:
- Changing what data gets extracted when analyzing a model
- Structural changes to how models are represented in the diagram
then you can subclass EntityRelationshipDiagram, ModelInfo, FieldInfo, and/or Edge to modify any behavior.
Warning
Changes like these depend on the internal APIs of erdantic and may be more likely to break between erdantic versions. If you're trying to do something like this, it would be nice to let the maintainers know in the repository discussions.
What to change¶
Here are some tips on what to change depending on your goals:
- To change the model-level data that is extracted and stored when analyzing a model...- Override ModelInfo.from_raw_model
 
- Override 
- To change the field-level data that is extracted and stored when analyzing a model...- Add or override a plugin's field extractor function and/or override FieldInfo.from_raw_type
 
- Add or override a plugin's field extractor function and/or override 
- To change the structure of the model tables in the diagram...- Override FieldInfo.to_dot_rowto change the DOT code for each field's row
- Override ModelInfo.to_dot_labelto change the table DOT code
 
- Override 
Get erdantic to use your subclasses¶
The best way to use custom subclasses is to subclass EntityRelationshipDiagram. Then, you can instantiate an instance of it and call its methods.
Example
from erdantic.core import EntityRelationshipDiagram
from erdantic.examples.pydantic import Party
class CustomEntityRelationshipDiagram(EntityRelationshipDiagram):
    ...
diagram = CustomEntityRelationshipDiagram()
diagram.add_model(Party)
diagram.draw("diagram.png")
Then, depending on which classes you're implementing subclasses of, you will want to do the following:
- If subclassing ModelInfo...- Also subclass EntityRelationshipDiagramand override the type annotation formodelsto use your custom subclass. The model info class used is determined by this type annotation.
 
- Also subclass 
- If subclassing FieldInfo...- Also subclass ModelInfoand override the the type annotation forfieldsto use your custom subclass.
- Add or override a plugin's field extractor function. The field info instances are instantiated in the field extractor function.
 
- Also subclass 
- If subclassing Edge...- Also subclass EntityRelationshipDiagramand override the type annotation foredgesto use your custom subclass. The edge class used is determined by this type annotation.
 
- Also subclass 
Example: Adding a column with default field values¶
Below is an example that has modified handling of Pydantic models. It extracts and stores the default value for fields, and it adds them as a third column to the tables in the diagram.
Here is how the rendered diagram looks:

And here is the source code:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |  |