Skip to content

erdantic.base

FT = TypeVar('FT', bound=Any, covariant=True) module-attribute

Type variable for a field object adapted by adapter class Field.

MT = TypeVar('MT', bound=type, covariant=True) module-attribute

Type variable for a data model class adapted by adapter class Model. Bounded by type.

model_adapter_registry: Dict[str, Type[Model]] = {} module-attribute

Registry of concrete Model adapter subclasses. A concrete Model subclass must be registered for it to be available to the diagram creation workflow.

Field

Bases: ABC, Generic[FT]

Abstract base class that adapts a field object of a data model class to work with erdantic. Concrete implementations should subclass and implement abstract methods.

Attributes:

Name Type Description
field FT

Field object on a data model class associated with this adapter

Source code in erdantic/base.py
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
class Field(ABC, Generic[FT]):
    """Abstract base class that adapts a field object of a data model class to work with erdantic.
    Concrete implementations should subclass and implement abstract methods.

    Attributes:
        field (FT): Field object on a data model class associated with this adapter
    """

    @abstractmethod
    def __init__(self, field: FT):
        """Initialize Field adapter instance.

        Args:
            field: Field object to associate with this adapter instance
        """
        self.field: Final[FT] = field

    @property
    @abstractmethod
    def name(self) -> str:  # pragma: no cover
        """Name of this field on the parent data model."""

    @property
    @abstractmethod
    def type_obj(self) -> Union[type, GenericAlias]:
        """Python type object for this field."""
        pass

    @abstractmethod
    def is_many(self) -> bool:  # pragma: no cover
        """Check whether this field represents a one-to-one or one-to-many relationship.

        Returns:
            bool: True if one-to-many relationship, else False.
        """
        pass

    @abstractmethod
    def is_nullable(self) -> bool:  # pragma: no cover
        """Check whether this field is nullable, i.e., can be `None`.

        Returns:
            bool: True if nullable, else False.
        """
        pass

    @property
    def type_name(self) -> str:  # pragma: no cover
        """String representation of the Python type annotation for this field."""
        return repr_type(self.type_obj)

    def dot_row(self) -> str:
        """Returns the DOT language "HTML-like" syntax specification of a row detailing this field
        that is part of a table describing the field's parent data model. It is used as part the
        `label` attribute of data model's node in the graph's DOT representation.

        Returns:
            str: DOT language for table row
        """
        return _row_template.format(name=self.name, type_name=self.type_name)

    def __eq__(self, other: Any) -> bool:
        return isinstance(other, type(self)) and hash(self) == hash(other)

    def __hash__(self) -> int:
        return id(self.field)

    def __repr__(self) -> str:
        return f"<{type(self).__name__}: '{self.name}', {self.type_name}>"

name: str abstractmethod property

Name of this field on the parent data model.

type_name: str property

String representation of the Python type annotation for this field.

type_obj: Union[type, GenericAlias] abstractmethod property

Python type object for this field.

__init__(field) abstractmethod

Initialize Field adapter instance.

Parameters:

Name Type Description Default
field FT

Field object to associate with this adapter instance

required
Source code in erdantic/base.py
24
25
26
27
28
29
30
31
@abstractmethod
def __init__(self, field: FT):
    """Initialize Field adapter instance.

    Args:
        field: Field object to associate with this adapter instance
    """
    self.field: Final[FT] = field

dot_row()

Returns the DOT language "HTML-like" syntax specification of a row detailing this field that is part of a table describing the field's parent data model. It is used as part the label attribute of data model's node in the graph's DOT representation.

Returns:

Name Type Description
str str

DOT language for table row

Source code in erdantic/base.py
67
68
69
70
71
72
73
74
75
def dot_row(self) -> str:
    """Returns the DOT language "HTML-like" syntax specification of a row detailing this field
    that is part of a table describing the field's parent data model. It is used as part the
    `label` attribute of data model's node in the graph's DOT representation.

    Returns:
        str: DOT language for table row
    """
    return _row_template.format(name=self.name, type_name=self.type_name)

is_many() abstractmethod

Check whether this field represents a one-to-one or one-to-many relationship.

Returns:

Name Type Description
bool bool

True if one-to-many relationship, else False.

Source code in erdantic/base.py
44
45
46
47
48
49
50
51
@abstractmethod
def is_many(self) -> bool:  # pragma: no cover
    """Check whether this field represents a one-to-one or one-to-many relationship.

    Returns:
        bool: True if one-to-many relationship, else False.
    """
    pass

is_nullable() abstractmethod

Check whether this field is nullable, i.e., can be None.

Returns:

Name Type Description
bool bool

True if nullable, else False.

Source code in erdantic/base.py
53
54
55
56
57
58
59
60
@abstractmethod
def is_nullable(self) -> bool:  # pragma: no cover
    """Check whether this field is nullable, i.e., can be `None`.

    Returns:
        bool: True if nullable, else False.
    """
    pass

Model

Bases: ABC, Generic[MT]

Abstract base class that adapts a data model class to work with erdantic. Instances represent a node in our entity relationship diagram graph. Concrete implementations should subclass and implement abstract methods.

Attributes:

Name Type Description
model MT

Data model class associated with this adapter

forward_ref_help Optional[str]

Instructions for how to resolve an unevaluated forward reference in a field's type declaration.

Source code in erdantic/base.py
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
class Model(ABC, Generic[MT]):
    """Abstract base class that adapts a data model class to work with erdantic. Instances
    represent a node in our entity relationship diagram graph. Concrete implementations should
    subclass and implement abstract methods.

    Attributes:
        model (MT): Data model class associated with this adapter
        forward_ref_help (Optional[str]): Instructions for how to resolve an unevaluated forward
            reference in a field's type declaration.
    """

    forward_ref_help: Optional[str] = None

    @abstractmethod
    def __init__(self, model: MT):
        """Initialize model adapter instance.

        Args:
            model: Data model class to associate with this adapter instance
        """
        self.model: Final[MT] = model

    @property
    @abstractmethod
    def fields(self) -> List[Field]:  # pragma: no cover
        """List of fields defined on this data model."""
        pass

    @staticmethod
    @abstractmethod
    def is_model_type(obj: Any) -> bool:  # pragma: no cover
        """Check if object is the type of data model class that this model adapter works with."""
        pass

    @property
    def name(self) -> str:  # pragma: no cover
        """Name of this data model."""
        return self.model.__name__

    @property
    def docstring(self) -> str:
        """Docstring for this data model."""
        out = f"{self.model.__module__}.{self.model.__qualname__}"
        docstring = inspect.getdoc(self.model)
        if docstring:
            out += "\n\n" + docstring + "\n"
        return out

    @property
    def key(self) -> str:
        """Human-readable unique identifier for this data model. Should be stable across
        sessions."""
        return f"{self.model.__module__}.{self.model.__qualname__}"

    def dot_label(self) -> str:
        """Returns the DOT language "HTML-like" syntax specification of a table for this data
        model. It is used as the `label` attribute of data model's node in the graph's DOT
        representation.

        Returns:
            str: DOT language for table
        """
        rows = "\n".join(field.dot_row() for field in self.fields)
        return _table_template.format(name=self.name, rows=rows).replace("\n", "")

    def __eq__(self, other) -> bool:
        return isinstance(other, type(self)) and hash(self) == hash(other)

    def __hash__(self) -> int:
        return hash(self.key)

    def __lt__(self, other) -> bool:
        if isinstance(other, Model):
            return self.key < other.key
        return NotImplemented

    def __repr__(self) -> str:
        return f"{type(self).__name__}({self.name})"

docstring: str property

Docstring for this data model.

fields: List[Field] abstractmethod property

List of fields defined on this data model.

key: str property

Human-readable unique identifier for this data model. Should be stable across sessions.

name: str property

Name of this data model.

__init__(model) abstractmethod

Initialize model adapter instance.

Parameters:

Name Type Description Default
model MT

Data model class to associate with this adapter instance

required
Source code in erdantic/base.py
113
114
115
116
117
118
119
120
@abstractmethod
def __init__(self, model: MT):
    """Initialize model adapter instance.

    Args:
        model: Data model class to associate with this adapter instance
    """
    self.model: Final[MT] = model

dot_label()

Returns the DOT language "HTML-like" syntax specification of a table for this data model. It is used as the label attribute of data model's node in the graph's DOT representation.

Returns:

Name Type Description
str str

DOT language for table

Source code in erdantic/base.py
154
155
156
157
158
159
160
161
162
163
def dot_label(self) -> str:
    """Returns the DOT language "HTML-like" syntax specification of a table for this data
    model. It is used as the `label` attribute of data model's node in the graph's DOT
    representation.

    Returns:
        str: DOT language for table
    """
    rows = "\n".join(field.dot_row() for field in self.fields)
    return _table_template.format(name=self.name, rows=rows).replace("\n", "")

is_model_type(obj) abstractmethod staticmethod

Check if object is the type of data model class that this model adapter works with.

Source code in erdantic/base.py
128
129
130
131
132
@staticmethod
@abstractmethod
def is_model_type(obj: Any) -> bool:  # pragma: no cover
    """Check if object is the type of data model class that this model adapter works with."""
    pass

register_model_adapter(type_name)

Create decorator to register a concrete Model adapter subclass that will be identified under the key type_name. A concrete Model subclass must be registered for it to be available to the diagram creation workflow.

Parameters:

Name Type Description Default
type_name str

Key used to identify concrete Model adapter subclass

required

Returns:

Type Description
Callable[[Type[Model]], Type[Model]]

Callable[[Type[Model]], Type[Model]]: A registration decorator for a concrete Model adapter subclass

Source code in erdantic/base.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def register_model_adapter(type_name: str) -> Callable[[Type[Model]], Type[Model]]:
    """Create decorator to register a concrete [`Model`][erdantic.base.Model] adapter subclass
    that will be identified under the key `type_name`. A concrete `Model` subclass must be
    registered for it to be available to the diagram creation workflow.

    Args:
        type_name (str): Key used to identify concrete `Model` adapter subclass

    Returns:
        Callable[[Type[Model]], Type[Model]]: A registration decorator for a concrete `Model`
            adapter subclass
    """

    def decorator(cls: type) -> type:
        global model_adapter_registry
        if not issubclass(cls, Model):
            raise InvalidModelAdapterError(
                "Only subclasses of erdantic.base.Model can be "
                "registered as erdantic model adapters."
            )
        model_adapter_registry[type_name] = cls
        return cls

    return decorator