Frameworks Overview¶
erdify recognizes five Python model frameworks from source and renders them into the same ERD format. This page shows the five frameworks side by side, how each is detected, and a worked example with the generated PlantUML. For Django-specific parsing details see Django ORM.
erdify also supports SQL DDL files (.sql) via the optional erdify[sql] extra —
see SQL DDL for details.
One Schema, Five Frameworks¶
The snippets below all describe the same User / Order schema — only the
syntax differs. Each one produces the identical diagram:

Info
The SQLModel, SQLAlchemy and Django versions declare keys explicitly (Django
via its implicit id and ForeignKey). Pydantic and dataclasses have no key
concept, so they are rendered with --infer-keys
(id → PK, <x>_id → FK) to match. The runnable sources live in
docs/examples/.
| SQLModel | SQLAlchemy 2.0 |
|---|---|
|
|
Pydantic --infer-keys | Dataclass --infer-keys |
|
|
| Django ORM | |
| |
How each framework is detected & parsed¶
| Framework | Detected by | Keys | Relationships |
|---|---|---|---|
| SQLModel | table=True |
Field(primary_key=…, foreign_key=…) |
Relationship(); back_populates= pairs both sides |
| SQLAlchemy 2.0 | __tablename__ + Mapped[...] members (also qualified, orm.Mapped[...], and WriteOnlyMapped / DynamicMapped) |
mapped_column(primary_key=…), ForeignKey(...) |
relationship() (lowercase); back_populates= pairs both sides |
| Django ORM | models.Model subclass |
primary_key=True or implicit id, ForeignKey/OneToOneField |
ForeignKey (N:1), OneToOneField (1:1), ManyToManyField (M:N, incl. through=) |
| Pydantic | BaseModel subclass (incl. transitive) |
--infer-keys only |
nested model refs (user: User, optional user: User \| None / Optional[User] / Union[User, None], collections such as list["Order"] / set[...] / tuple[X, ...], also inside Annotated[...]) |
| Dataclass | @dataclass decorator |
--infer-keys only |
nested model refs |
Info
Mixins / abstract bases (e.g. a SQLAlchemy mixin without __tablename__, or a
Django class Meta: abstract = True base) are not drawn as tables, but their
columns are inherited into concrete entities. A Django model subclassing a
concrete model is the exception: it gets its own table linked 1:1 to the
parent (multi-table inheritance). An attribute declared in more
than one base resolves like Python's MRO: the class body wins, then the
first base that declares it.
SQL DDL (.sql files parsed via sqlglot) uses a separate file-based detection
mechanism and is not listed in the AST-detection table above. See SQL DDL.
Worked example¶
Given these SQLModel definitions:
from enum import Enum
from sqlmodel import SQLModel, Field, Relationship
class UserRole(Enum):
ADMIN = "admin"
USER = "user"
class User(SQLModel, table=True):
__tablename__: str = "user"
id: int = Field(primary_key=True)
name: str
email: str = Field(index=True)
role: UserRole = Field(default=UserRole.USER)
orders: list["Order"] = Relationship(back_populates="user")
class Order(SQLModel, table=True):
__tablename__: str = "order"
id: int = Field(primary_key=True)
user_id: int = Field(foreign_key="user.id")
total: float
user: "User" = Relationship(back_populates="orders")
The tool generates:

with following code:
@startuml Database ERD
!define primary_key(x) <b><color:#b8861b><&key></color> x</b>
!define foreign_key(x) <color:#aaaaaa><&key></color> x
!define column(x) <color:#efefef><&media-record></color> x
skinparam linetype ortho
' Enums
enum UserRole << (E,#FFCC00) >> {
ADMIN
USER
}
' Entities
entity "user" as User {
primary_key(id) : int
column(name) : str
column(email) : str
column(role) : UserRole = USER
}
entity "order" as Order {
primary_key(id) : int
foreign_key(user_id) : int
column(total) : float
}
' Relationships
Order }o--|| User : "user_id"
@enduml