0
# Model Information Classes
1
2
Erdantic uses several internal data structures to represent analyzed models, their fields, and relationships. These classes provide the foundation for diagram generation and can be used for advanced programmatic manipulation of diagram data.
3
4
## Core Information Classes
5
6
```python { .api }
7
class ModelInfo(pydantic.BaseModel):
8
"""Holds information about an analyzed data model class.
9
10
Attributes:
11
full_name (FullyQualifiedName): Fully qualified name of the data model class.
12
name (str): Name of the data model class.
13
fields (Dict[str, FieldInfo]): A mapping to FieldInfo instances for each field of the data
14
model class.
15
description (str): Docstring or other description of the data model class.
16
"""
17
18
@classmethod
19
def from_raw_model(cls, raw_model: type) -> Self:
20
"""Constructor method to create a new instance from a raw data model class.
21
22
Args:
23
raw_model (type): Data model class.
24
25
Returns:
26
Self: New instance of ModelInfo.
27
"""
28
29
@property
30
def key(self) -> str:
31
"""Returns the key used to identify this instance of ModelInfo in the
32
EntityRelationshipDiagram.models mapping. This value is the string representation of the
33
`full_name` field.
34
"""
35
36
@property
37
def raw_model(self) -> type:
38
"""Returns the raw data model class. This is a cached property. If the raw model is not
39
already known, it will attempt to import the data model class.
40
"""
41
42
def to_dot_label(self) -> str:
43
"""Returns the DOT language "HTML-like" syntax specification of a table for this data
44
model. It is used as the `label` attribute of data model's node in the graph's DOT
45
representation.
46
47
Returns:
48
str: DOT language for table
49
"""
50
51
class FieldInfo(pydantic.BaseModel):
52
"""Holds information about a field of an analyzed data model class.
53
54
Attributes:
55
model_full_name (FullyQualifiedName): Fully qualified name of the data model class that
56
the field belongs to.
57
name (str): Name of the field.
58
type_name (str): String representation of the field's type.
59
"""
60
61
@classmethod
62
def from_raw_type(cls, model_full_name: FullyQualifiedName, name: str, raw_type: type) -> Self:
63
"""Constructor method to create a new instance from a raw type annotation.
64
65
Args:
66
model_full_name (FullyQualifiedName): Fully qualified name of the data model class that
67
the field belongs to.
68
name (str): Name of field.
69
raw_type (type): Type annotation.
70
71
Returns:
72
Self: New FieldInfo instance.
73
"""
74
75
@property
76
def key(self) -> str:
77
"""Returns the key used to identify this instance of FieldInfo in the ModelInfo.fields
78
mapping. This value is the value in the 'name' field.
79
"""
80
81
@property
82
def raw_type(self) -> type:
83
"""Returns the raw type annotation of the field. This is a cached property. If the raw
84
type is not already known, it will attempt to import the data model class and reextract
85
the field's type annotation.
86
87
Raises:
88
FieldNotFoundError: If field name doesn't match any fields returned by field extractor.
89
UnknownModelTypeError: If model type is not recognized by any plugin.
90
91
Returns:
92
type: Type annotation.
93
"""
94
95
def to_dot_row(self) -> str:
96
"""Returns the DOT language "HTML-like" syntax specification of a row detailing this field
97
that is part of a table describing the field's parent data model. It is used as part the
98
`label` attribute of data model's node in the graph's DOT representation.
99
100
Returns:
101
str: DOT language for table row
102
"""
103
104
class Edge(pydantic.BaseModel):
105
"""Hold information about a relationship between two data model classes. These represent
106
directed edges in the entity relationship diagram.
107
108
Attributes:
109
source_model_full_name (FullyQualifiedName): Fully qualified name of the source model,
110
i.e., the model that contains a field that references the target model.
111
source_field_name (str): Name of the field on the source model that references the target
112
model.
113
target_model_full_name (FullyQualifiedName): Fully qualified name of the target model,
114
i.e., the model that is referenced by the source model's field.
115
target_cardinality (Cardinality): Cardinality of the target model in the relationship,
116
e.g., if the relationship is one (source) to many (target), this value will be
117
`Cardinality.MANY`.
118
target_modality (Modality): Modality of the target model in the relationship, e.g., if the
119
relationship is one (source) to zero (target), meaning that the target is optional,
120
this value will be `Modality.ZERO`.
121
source_cardinality (Cardinality): Cardinality of the source model in the
122
relationship. Defaults to Cardinality.UNSPECIFIED. This will never be set for Edges
123
created by erdantic, but you can set it manually to notate an externally known cardinality.
124
source_modality (Modality): Modality of the source model in the relationship.
125
Defaults to Modality.UNSPECIFIED. This will never be set for Edges created by erdantic,
126
but you can set it manually to notate an externally known modality.
127
"""
128
129
@property
130
def key(self) -> str:
131
"""Returns the key used to identify this instance of Edge in the
132
EntityRelationshipDiagram.edges mapping. This value is a hyphenated string of the fields
133
`source_model_full_name`, `source_field_name`, and `target_model_full_name`.
134
"""
135
136
@classmethod
137
def from_field_info(cls, target_model: type, source_field_info: FieldInfo) -> Self:
138
"""Constructor method to create a new instance from a target model instance and a source
139
model's FieldInfo.
140
141
Args:
142
target_model (type): Target model class.
143
source_field_info (FieldInfo): FieldInfo instance for the field on the source model
144
that references the target model.
145
146
Returns:
147
Self: New instance of Edge.
148
"""
149
150
def target_dot_arrow_shape(self) -> str:
151
"""Arrow shape specification in Graphviz DOT language for this edge's head (the end at the
152
target model). See [Graphviz docs](https://graphviz.org/doc/info/arrows.html) as a
153
reference. Shape returned is based on
154
[crow's foot notation](https://www.gleek.io/blog/crows-foot-notation) for the
155
relationship's cardinality and modality.
156
157
Returns:
158
str: DOT language specification for arrow shape of this edge's head
159
"""
160
161
def source_dot_arrow_shape(self) -> str:
162
"""Arrow shape specification in Graphviz DOT language for this edge's tail (the end at the
163
source model). See [Graphviz docs](https://graphviz.org/doc/info/arrows.html) as a
164
reference. Shape returned is based on
165
[crow's foot notation](https://www.gleek.io/blog/crows-foot-notation) for the
166
relationship's cardinality and modality.
167
168
Returns:
169
str: DOT language specification for arrow shape of this edge's tail
170
"""
171
172
@total_ordering
173
class FullyQualifiedName(pydantic.BaseModel):
174
"""Holds the fully qualified name components (module and qualified name) of a Python object.
175
This is used to uniquely identify an object, can be used to import it.
176
177
Attributes:
178
module (str): Name of the module that the object is defined in.
179
qual_name (str): Qualified name of the object.
180
"""
181
182
@classmethod
183
def from_object(cls, obj: Any) -> Self:
184
"""Constructor method to create a new instance from a Python object.
185
186
Args:
187
obj (Any): Python object.
188
189
Returns:
190
Self: Fully qualified name of the object.
191
"""
192
193
def import_object(self) -> Any:
194
"""Imports the object from the module and returns it.
195
196
Returns:
197
Any: Object referenced by this FullyQualifiedName instance.
198
"""
199
200
def __str__(self) -> str:
201
"""Returns string representation as 'module.qual_name'."""
202
203
def __hash__(self) -> int:
204
"""Returns hash based on module and qual_name."""
205
206
def __lt__(self, other: Self) -> bool:
207
"""Comparison operator for sorting."""
208
```
209
210
## Enumerations
211
212
```python { .api }
213
class Cardinality(Enum):
214
"""Enumeration of possible cardinality values for a relationship between two data model
215
classes. Cardinality measures the maximum number of associations.
216
"""
217
218
UNSPECIFIED = "unspecified"
219
ONE = "one"
220
MANY = "many"
221
222
def to_dot(self) -> str:
223
"""Returns the DOT language specification for the arrowhead styling associated with the
224
cardinality value.
225
"""
226
227
class Modality(Enum):
228
"""Enumeration of possible modality values for a relationship between two data model
229
classes. Modality measures the minimum number of associations.
230
"""
231
232
UNSPECIFIED = "unspecified"
233
ZERO = "zero"
234
ONE = "one"
235
236
def to_dot(self) -> str:
237
"""Returns the DOT language specification for the arrowhead styling associated with the
238
modality value.
239
"""
240
```
241
242
## Required Imports
243
244
```python
245
from erdantic.core import (
246
ModelInfo, FieldInfo, Edge, FullyQualifiedName,
247
Cardinality, Modality
248
)
249
from enum import Enum
250
from typing import Any
251
from functools import total_ordering
252
import pydantic
253
```
254
255
## Usage Examples
256
257
### Accessing Diagram Information
258
259
```python
260
from erdantic import create
261
from pydantic import BaseModel
262
263
class User(BaseModel):
264
name: str
265
email: str
266
267
class Post(BaseModel):
268
title: str
269
author: User
270
271
# Create diagram
272
diagram = create(User, Post)
273
274
# Access model information
275
for model_key, model_info in diagram.models.items():
276
print(f"Model: {model_info.name}")
277
print(f" Full name: {model_info.full_name}")
278
print(f" Description: {model_info.description}")
279
280
# Access field information
281
for field_name, field_info in model_info.fields.items():
282
print(f" Field: {field_info.name} ({field_info.type_name})")
283
284
# Access relationship information
285
for edge_key, edge in diagram.edges.items():
286
print(f"Relationship: {edge.source_model_full_name}.{edge.source_field_name} -> {edge.target_model_full_name}")
287
print(f" Target cardinality: {edge.target_cardinality.value}")
288
print(f" Target modality: {edge.target_modality.value}")
289
```
290
291
### Working with FullyQualifiedName
292
293
```python
294
from erdantic.core import FullyQualifiedName
295
296
# Create from object
297
fqn = FullyQualifiedName.from_object(User)
298
print(f"Module: {fqn.module}") # __main__ (or actual module)
299
print(f"Qualified name: {fqn.qual_name}") # User
300
print(f"Full name: {str(fqn)}") # __main__.User
301
302
# Import object back
303
imported_class = fqn.import_object()
304
print(imported_class == User) # True
305
```
306
307
### Manual ModelInfo Creation
308
309
```python
310
from erdantic.core import ModelInfo, FieldInfo, FullyQualifiedName
311
312
# Create model info manually (normally done automatically)
313
model_fqn = FullyQualifiedName.from_object(User)
314
model_info = ModelInfo.from_raw_model(User)
315
316
print(f"Model name: {model_info.name}")
317
print(f"Model key: {model_info.key}")
318
print(f"Fields: {list(model_info.fields.keys())}")
319
320
# Access the original model class
321
original_model = model_info.raw_model
322
print(original_model == User) # True
323
```
324
325
### Manual FieldInfo Creation
326
327
```python
328
from erdantic.core import FieldInfo, FullyQualifiedName
329
330
# Create field info manually
331
model_fqn = FullyQualifiedName.from_object(User)
332
field_info = FieldInfo.from_raw_type(
333
model_full_name=model_fqn,
334
name="name",
335
raw_type=str
336
)
337
338
print(f"Field name: {field_info.name}")
339
print(f"Field type: {field_info.type_name}")
340
print(f"Field key: {field_info.key}")
341
342
# Access raw type
343
raw_type = field_info.raw_type
344
print(raw_type == str) # True
345
```
346
347
### Understanding Relationships
348
349
```python
350
# Examine edge details
351
for edge in diagram.edges.values():
352
print(f"Source: {edge.source_model_full_name}")
353
print(f"Source field: {edge.source_field_name}")
354
print(f"Target: {edge.target_model_full_name}")
355
356
# Cardinality and modality
357
print(f"Target cardinality: {edge.target_cardinality}") # ONE or MANY
358
print(f"Target modality: {edge.target_modality}") # ZERO, ONE, or UNSPECIFIED
359
360
# DOT arrow shapes
361
print(f"Target arrow: {edge.target_dot_arrow_shape()}")
362
print(f"Source arrow: {edge.source_dot_arrow_shape()}")
363
```
364
365
### Custom Edge Creation
366
367
```python
368
from erdantic.core import Edge, Cardinality, Modality, FullyQualifiedName
369
370
# Create custom edge manually
371
user_fqn = FullyQualifiedName.from_object(User)
372
post_fqn = FullyQualifiedName.from_object(Post)
373
374
custom_edge = Edge(
375
source_model_full_name=post_fqn,
376
source_field_name="author",
377
target_model_full_name=user_fqn,
378
target_cardinality=Cardinality.ONE,
379
target_modality=Modality.ONE,
380
source_cardinality=Cardinality.MANY, # Optional: many posts per user
381
source_modality=Modality.ZERO # Optional: user might have no posts
382
)
383
384
print(f"Edge key: {custom_edge.key}")
385
```
386
387
### DOT Generation from Components
388
389
```python
390
# Generate DOT representations of individual components
391
model_info = diagram.models[str(FullyQualifiedName.from_object(User))]
392
dot_label = model_info.to_dot_label()
393
print("Model DOT label:", dot_label)
394
395
field_info = model_info.fields["name"]
396
dot_row = field_info.to_dot_row()
397
print("Field DOT row:", dot_row)
398
```
399
400
### Working with Cardinality and Modality
401
402
```python
403
from erdantic.core import Cardinality, Modality
404
405
# All cardinality values
406
print("Cardinalities:")
407
for card in Cardinality:
408
print(f" {card.value}: {card.to_dot()}")
409
410
# All modality values
411
print("Modalities:")
412
for mod in Modality:
413
print(f" {mod.value}: {mod.to_dot()}")
414
415
# Combined arrow shapes (cardinality + modality)
416
one_required = Cardinality.ONE.to_dot() + Modality.ONE.to_dot()
417
many_optional = Cardinality.MANY.to_dot() + Modality.ZERO.to_dot()
418
print(f"One required: {one_required}") # noneteetee
419
print(f"Many optional: {many_optional}") # crowodot
420
```