erdify 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erdify-0.3.0/LICENSE +21 -0
- erdify-0.3.0/PKG-INFO +637 -0
- erdify-0.3.0/README.md +606 -0
- erdify-0.3.0/pyproject.toml +67 -0
- erdify-0.3.0/src/erdify/__init__.py +27 -0
- erdify-0.3.0/src/erdify/__main__.py +6 -0
- erdify-0.3.0/src/erdify/cli.py +114 -0
- erdify-0.3.0/src/erdify/config.py +41 -0
- erdify-0.3.0/src/erdify/generator.py +322 -0
- erdify-0.3.0/src/erdify/parser.py +519 -0
- erdify-0.3.0/src/erdify/py.typed +0 -0
erdify-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Devsuit GmbH
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
erdify-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: erdify
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Generate PlantUML ERD diagrams from SQLModel, SQLAlchemy, Pydantic and dataclass models
|
|
5
|
+
Keywords: sqlmodel,sqlalchemy,pydantic,dataclass,erd,plantuml,diagram,database,erdify
|
|
6
|
+
Author: devsuit GmbH, Fabian Clemenz
|
|
7
|
+
Author-email: devsuit GmbH <tech@devsuit.de>, Fabian Clemenz <fabian.clemenz@devsuit.de>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Requires-Dist: pytest ; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-cov ; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff ; extra == 'dev'
|
|
23
|
+
Requires-Dist: mypy ; extra == 'dev'
|
|
24
|
+
Requires-Dist: sqlmodel ; extra == 'dev'
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Project-URL: Homepage, https://github.com/devsuit-berlin/erdify
|
|
27
|
+
Project-URL: Repository, https://github.com/devsuit-berlin/erdify
|
|
28
|
+
Project-URL: Documentation, https://github.com/devsuit-berlin/erdify#readme
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# ποΈ erdify
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/erdify/)
|
|
35
|
+
[](https://pypi.org/project/erdify/)
|
|
36
|
+
[](https://opensource.org/licenses/MIT)
|
|
37
|
+
[](https://github.com/devsuit-berlin/erdify/actions/workflows/test.yml)
|
|
38
|
+
[](https://github.com/devsuit-berlin/erdify/actions/workflows/lint.yml)
|
|
39
|
+
[](https://github.com/astral-sh/ruff)
|
|
40
|
+
[](https://mypy-lang.org/)
|
|
41
|
+
|
|
42
|
+
> π Generate beautiful PlantUML Entity Relationship Diagrams from your SQLModel, SQLAlchemy, Pydantic and dataclass models automatically!
|
|
43
|
+
|
|
44
|
+
**erdify** parses your model files using AST (Abstract Syntax Tree) and generates comprehensive ERD diagrams in PlantUML format. It supports SQLModel, SQLAlchemy 2.0, Pydantic and standard-library dataclasses. No database connection required!
|
|
45
|
+
|
|
46
|
+
## β¨ Features
|
|
47
|
+
|
|
48
|
+
- π **Automatic ERD Generation** - Parse your models and generate PlantUML diagrams
|
|
49
|
+
- 𧬠**4 Frameworks** - SQLModel, SQLAlchemy 2.0 (`Mapped[...]`/`mapped_column()`), Pydantic and dataclasses
|
|
50
|
+
- π **AST-Based Parsing** - No imports needed, works with any valid Python code
|
|
51
|
+
- π― **Zero Runtime Dependencies** - Uses only Python standard library
|
|
52
|
+
- π **Relationship Detection** - Automatically detects foreign keys and relationships
|
|
53
|
+
- π **Key Inference** - Optional `--infer-keys` derives PK/FK from field names for keyless models
|
|
54
|
+
- π« **Exclude Patterns** - Filter out entities by class or table name with glob patterns
|
|
55
|
+
- π¦ **Inheritance Support** - Correctly resolves fields from base classes and mixins
|
|
56
|
+
- π·οΈ **Enum Support** - Includes enum definitions in the diagram
|
|
57
|
+
- π **Link Table Detection** - Identifies many-to-many relationship tables
|
|
58
|
+
- π¨ **Beautiful Output** - Clean, readable PlantUML with proper styling
|
|
59
|
+
|
|
60
|
+
## π¦ Installation
|
|
61
|
+
|
|
62
|
+
### Using pip
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pip install erdify
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Using uv
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
uv add erdify
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Using pipx (recommended for CLI usage)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pipx install erdify
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Using uvx (no installation needed)
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Run directly without installing
|
|
84
|
+
uvx erdify ./src/database -o erd.puml
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## π Quick Start
|
|
88
|
+
|
|
89
|
+
### Command Line
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Generate ERD from a models directory
|
|
93
|
+
erdify ./src/database -o erd.puml
|
|
94
|
+
|
|
95
|
+
# With custom title
|
|
96
|
+
erdify ./src/models --title "My Database Schema" -o schema.puml
|
|
97
|
+
|
|
98
|
+
# Output to stdout
|
|
99
|
+
erdify ./src/database
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Python API
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from pathlib import Path
|
|
106
|
+
from erdify import parse_models_directory, generate_plantuml
|
|
107
|
+
|
|
108
|
+
# Parse your models
|
|
109
|
+
entities, enums = parse_models_directory(Path("./src/database"))
|
|
110
|
+
|
|
111
|
+
# Generate PlantUML
|
|
112
|
+
diagram = generate_plantuml(
|
|
113
|
+
entities=entities,
|
|
114
|
+
enums=enums,
|
|
115
|
+
title="My Database ERD"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Save or use the diagram
|
|
119
|
+
Path("erd.puml").write_text(diagram)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## π Usage
|
|
123
|
+
|
|
124
|
+
### CLI Options
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
usage: erdify [-h] [-o OUTPUT] [--title TITLE] [--exclude [PATTERN ...]]
|
|
128
|
+
[--infer-keys] [--no-enums] [--no-relationships] [-v]
|
|
129
|
+
input
|
|
130
|
+
|
|
131
|
+
Generate PlantUML ERD diagrams from SQLModel, SQLAlchemy, Pydantic and dataclass models
|
|
132
|
+
|
|
133
|
+
positional arguments:
|
|
134
|
+
input Directory containing model files (searches for models.py recursively)
|
|
135
|
+
|
|
136
|
+
options:
|
|
137
|
+
-h, --help show this help message and exit
|
|
138
|
+
-o OUTPUT, --output OUTPUT
|
|
139
|
+
Output .puml file (default: stdout)
|
|
140
|
+
--title TITLE Diagram title (default: 'Database ERD')
|
|
141
|
+
--exclude [PATTERN ...]
|
|
142
|
+
Glob patterns (case-sensitive) to exclude entities by
|
|
143
|
+
class name or table name, e.g. --exclude '*Link' audit_log
|
|
144
|
+
--infer-keys For keyless models (Pydantic/dataclass), infer a primary
|
|
145
|
+
key from a field named 'id' and a foreign key from '<x>_id'
|
|
146
|
+
--no-enums Skip enum definitions in output
|
|
147
|
+
--no-relationships Skip relationship lines in output
|
|
148
|
+
-v, --version show program's version number and exit
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Excluding Entities
|
|
152
|
+
|
|
153
|
+
Use `--exclude` to drop tables/entities from the diagram. Each pattern is a
|
|
154
|
+
case-sensitive [glob](https://docs.python.org/3/library/fnmatch.html) tested
|
|
155
|
+
against both the **class name** and the **table name** β an entity is excluded
|
|
156
|
+
if either matches. Any relationships pointing at an excluded entity are dropped
|
|
157
|
+
too, so no dangling lines remain.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Exclude all link tables (class names ending in "Link")
|
|
161
|
+
erdify ./src/database --exclude '*Link'
|
|
162
|
+
|
|
163
|
+
# Exclude by table name, with multiple patterns
|
|
164
|
+
erdify ./src/database --exclude audit_log 'tmp_*' Session
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
> π‘ Quote patterns containing `*` so your shell doesn't expand them.
|
|
168
|
+
|
|
169
|
+
### Running as Module
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
python -m erdify ./src/database -o erd.puml
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Example Models
|
|
176
|
+
|
|
177
|
+
Given these SQLModel definitions:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from enum import Enum
|
|
181
|
+
from sqlmodel import SQLModel, Field, Relationship
|
|
182
|
+
|
|
183
|
+
class UserRole(Enum):
|
|
184
|
+
ADMIN = "admin"
|
|
185
|
+
USER = "user"
|
|
186
|
+
|
|
187
|
+
class User(SQLModel, table=True):
|
|
188
|
+
__tablename__: str = "user"
|
|
189
|
+
|
|
190
|
+
id: int = Field(primary_key=True)
|
|
191
|
+
name: str
|
|
192
|
+
email: str = Field(index=True)
|
|
193
|
+
role: UserRole = Field(default=UserRole.USER)
|
|
194
|
+
|
|
195
|
+
orders: list["Order"] = Relationship(back_populates="user")
|
|
196
|
+
|
|
197
|
+
class Order(SQLModel, table=True):
|
|
198
|
+
__tablename__: str = "order"
|
|
199
|
+
|
|
200
|
+
id: int = Field(primary_key=True)
|
|
201
|
+
user_id: int = Field(foreign_key="user.id")
|
|
202
|
+
total: float
|
|
203
|
+
|
|
204
|
+
user: "User" = Relationship(back_populates="orders")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
The tool generates:
|
|
208
|
+
|
|
209
|
+

|
|
210
|
+
|
|
211
|
+
with following code:
|
|
212
|
+
|
|
213
|
+
```plantuml
|
|
214
|
+
@startuml Database ERD
|
|
215
|
+
!define primary_key(x) <b><color:#b8861b><&key></color> x</b>
|
|
216
|
+
!define foreign_key(x) <color:#aaaaaa><&key></color> x
|
|
217
|
+
!define column(x) <color:#efefef><&media-record></color> x
|
|
218
|
+
|
|
219
|
+
skinparam linetype ortho
|
|
220
|
+
|
|
221
|
+
' Enums
|
|
222
|
+
enum UserRole << (E,#FFCC00) >> {
|
|
223
|
+
ADMIN
|
|
224
|
+
USER
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
' Entities
|
|
228
|
+
entity "user" as User {
|
|
229
|
+
primary_key(id) : int
|
|
230
|
+
column(name) : str
|
|
231
|
+
column(email) : str
|
|
232
|
+
column(role) : UserRole = USER
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
entity "order" as Order {
|
|
236
|
+
primary_key(id) : int
|
|
237
|
+
foreign_key(user_id) : int
|
|
238
|
+
column(total) : float
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
' Relationships
|
|
242
|
+
Order }o--|| User : "user_id"
|
|
243
|
+
|
|
244
|
+
@enduml
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## 𧬠One Schema, Four Frameworks
|
|
248
|
+
|
|
249
|
+
erdify supports four model frameworks. The snippets below all describe the
|
|
250
|
+
**same** `User` / `Order` schema β only the syntax differs. Each one produces the
|
|
251
|
+
**identical** diagram:
|
|
252
|
+
|
|
253
|
+

|
|
254
|
+
|
|
255
|
+
> βΉοΈ The SQLModel and SQLAlchemy versions declare keys explicitly. Pydantic and
|
|
256
|
+
> dataclasses have no key concept, so they are rendered with [`--infer-keys`](#inferring-keys---infer-keys)
|
|
257
|
+
> (`id` β PK, `<x>_id` β FK) to match. The runnable sources live in
|
|
258
|
+
> [`docs/examples/`](https://github.com/devsuit-berlin/erdify/tree/main/docs/examples).
|
|
259
|
+
|
|
260
|
+
<table>
|
|
261
|
+
<tr><th>SQLModel</th><th>SQLAlchemy 2.0</th></tr>
|
|
262
|
+
<tr><td>
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from sqlmodel import (
|
|
266
|
+
Field, Relationship, SQLModel,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class User(SQLModel, table=True):
|
|
271
|
+
__tablename__: str = "user"
|
|
272
|
+
id: int = Field(primary_key=True)
|
|
273
|
+
name: str
|
|
274
|
+
email: str
|
|
275
|
+
orders: list["Order"] = Relationship(
|
|
276
|
+
back_populates="user")
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class Order(SQLModel, table=True):
|
|
280
|
+
__tablename__: str = "order"
|
|
281
|
+
id: int = Field(primary_key=True)
|
|
282
|
+
user_id: int = Field(
|
|
283
|
+
foreign_key="user.id")
|
|
284
|
+
total: float
|
|
285
|
+
user: "User" = Relationship(
|
|
286
|
+
back_populates="orders")
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
</td><td>
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from sqlalchemy import ForeignKey
|
|
293
|
+
from sqlalchemy.orm import (
|
|
294
|
+
DeclarativeBase, Mapped,
|
|
295
|
+
mapped_column, relationship,
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class Base(DeclarativeBase): ...
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class User(Base):
|
|
303
|
+
__tablename__ = "user"
|
|
304
|
+
id: Mapped[int] = mapped_column(
|
|
305
|
+
primary_key=True)
|
|
306
|
+
name: Mapped[str] = mapped_column()
|
|
307
|
+
email: Mapped[str] = mapped_column()
|
|
308
|
+
orders: Mapped[list["Order"]] = (
|
|
309
|
+
relationship(back_populates="user"))
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class Order(Base):
|
|
313
|
+
__tablename__ = "order"
|
|
314
|
+
id: Mapped[int] = mapped_column(
|
|
315
|
+
primary_key=True)
|
|
316
|
+
user_id: Mapped[int] = mapped_column(
|
|
317
|
+
ForeignKey("user.id"))
|
|
318
|
+
total: Mapped[float] = mapped_column()
|
|
319
|
+
user: Mapped["User"] = relationship(
|
|
320
|
+
back_populates="orders")
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
</td></tr>
|
|
324
|
+
<tr><th>Pydantic <code>--infer-keys</code></th><th>Dataclass <code>--infer-keys</code></th></tr>
|
|
325
|
+
<tr><td>
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from pydantic import BaseModel
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class User(BaseModel):
|
|
332
|
+
id: int
|
|
333
|
+
name: str
|
|
334
|
+
email: str
|
|
335
|
+
orders: list["Order"] = []
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class Order(BaseModel):
|
|
339
|
+
id: int
|
|
340
|
+
user_id: int
|
|
341
|
+
total: float
|
|
342
|
+
user: "User"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
</td><td>
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
from dataclasses import dataclass, field
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@dataclass
|
|
352
|
+
class User:
|
|
353
|
+
id: int
|
|
354
|
+
name: str
|
|
355
|
+
email: str
|
|
356
|
+
orders: list["Order"] = field(
|
|
357
|
+
default_factory=list)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@dataclass
|
|
361
|
+
class Order:
|
|
362
|
+
id: int
|
|
363
|
+
user_id: int
|
|
364
|
+
total: float
|
|
365
|
+
user: "User" = None
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
</td></tr>
|
|
369
|
+
</table>
|
|
370
|
+
|
|
371
|
+
**How each framework is detected & parsed:**
|
|
372
|
+
|
|
373
|
+
| Framework | Detected by | Keys | Relationships |
|
|
374
|
+
| --------- | ----------- | ---- | ------------- |
|
|
375
|
+
| SQLModel | `table=True` | `Field(primary_key=β¦, foreign_key=β¦)` | `Relationship()` |
|
|
376
|
+
| SQLAlchemy 2.0 | `__tablename__` + `Mapped[...]` columns | `mapped_column(primary_key=β¦)`, `ForeignKey(...)` | `relationship()` (lowercase) |
|
|
377
|
+
| Pydantic | `BaseModel` subclass (incl. transitive) | `--infer-keys` only | nested model refs (`user: User`, `list["Order"]`) |
|
|
378
|
+
| Dataclass | `@dataclass` decorator | `--infer-keys` only | nested model refs |
|
|
379
|
+
|
|
380
|
+
> βΉοΈ Mixins / abstract bases (e.g. a SQLAlchemy mixin without `__tablename__`)
|
|
381
|
+
> are not drawn as tables, but their columns are inherited into concrete
|
|
382
|
+
> entities. Imports aliased to other names (e.g. `mapped_column as mc`) are not
|
|
383
|
+
> detected.
|
|
384
|
+
|
|
385
|
+
### Inferring keys (`--infer-keys`)
|
|
386
|
+
|
|
387
|
+
Pydantic models and dataclasses have **no database key concept**. By default all
|
|
388
|
+
fields are rendered as plain columns and relationships come only from nested
|
|
389
|
+
model references. If your models follow a database-like naming convention, pass
|
|
390
|
+
`--infer-keys` to derive keys from field names:
|
|
391
|
+
|
|
392
|
+
- a field named **`id`** β **primary key**
|
|
393
|
+
- a field named **`<x>_id`** β **foreign key** targeting table **`<x>`**
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
# Plain columns (default)
|
|
397
|
+
erdify ./src/schemas
|
|
398
|
+
|
|
399
|
+
# Infer PK/FK from id / <x>_id naming
|
|
400
|
+
erdify ./src/schemas --infer-keys
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
> βΉοΈ `--infer-keys` only affects Pydantic/dataclass models. SQLModel and
|
|
404
|
+
> SQLAlchemy keys are always read from the explicit definitions and never
|
|
405
|
+
> overridden.
|
|
406
|
+
|
|
407
|
+
## π¨ Viewing the Diagram
|
|
408
|
+
|
|
409
|
+
### Online
|
|
410
|
+
|
|
411
|
+
1. Copy the generated `.puml` content
|
|
412
|
+
2. Paste at [PlantUML Web Server](http://www.plantuml.com/plantuml/uml/)
|
|
413
|
+
|
|
414
|
+
### Local with PlantUML
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
# Install PlantUML (macOS)
|
|
418
|
+
brew install plantuml
|
|
419
|
+
|
|
420
|
+
# Generate PNG
|
|
421
|
+
plantuml erd.puml
|
|
422
|
+
|
|
423
|
+
# Generate SVG
|
|
424
|
+
plantuml -tsvg erd.puml
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### VS Code Extension
|
|
428
|
+
|
|
429
|
+
Install the [PlantUML extension](https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml) for live preview.
|
|
430
|
+
|
|
431
|
+
## π§ Advanced Usage
|
|
432
|
+
|
|
433
|
+
### Programmatic Access
|
|
434
|
+
|
|
435
|
+
```python
|
|
436
|
+
from erdify import (
|
|
437
|
+
ASTDatabaseParser,
|
|
438
|
+
PlantUMLGenerator,
|
|
439
|
+
EntityInfo,
|
|
440
|
+
FieldInfo,
|
|
441
|
+
EnumInfo,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Low-level parser access
|
|
445
|
+
parser = ASTDatabaseParser(Path("./models"))
|
|
446
|
+
entities, enums = parser.parse_all_models()
|
|
447
|
+
|
|
448
|
+
# Access entity details
|
|
449
|
+
for name, entity in entities.items():
|
|
450
|
+
print(f"Table: {entity.table_name}")
|
|
451
|
+
for field in entity.fields:
|
|
452
|
+
if field.is_primary_key:
|
|
453
|
+
print(f" PK: {field.name}")
|
|
454
|
+
elif field.is_foreign_key:
|
|
455
|
+
print(f" FK: {field.name} -> {field.foreign_table}")
|
|
456
|
+
|
|
457
|
+
# Custom generator with options
|
|
458
|
+
generator = PlantUMLGenerator(
|
|
459
|
+
entities=entities,
|
|
460
|
+
enums=enums,
|
|
461
|
+
title="Custom ERD"
|
|
462
|
+
)
|
|
463
|
+
output = generator.generate()
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Integration with CI/CD
|
|
467
|
+
|
|
468
|
+
```yaml
|
|
469
|
+
# .github/workflows/docs.yml
|
|
470
|
+
name: Generate ERD
|
|
471
|
+
|
|
472
|
+
on:
|
|
473
|
+
push:
|
|
474
|
+
paths:
|
|
475
|
+
- 'src/database/**'
|
|
476
|
+
|
|
477
|
+
jobs:
|
|
478
|
+
generate-erd:
|
|
479
|
+
runs-on: ubuntu-latest
|
|
480
|
+
steps:
|
|
481
|
+
- uses: actions/checkout@v4
|
|
482
|
+
|
|
483
|
+
- name: Set up Python
|
|
484
|
+
uses: actions/setup-python@v5
|
|
485
|
+
with:
|
|
486
|
+
python-version: '3.12'
|
|
487
|
+
|
|
488
|
+
- name: Install erdify
|
|
489
|
+
run: pip install erdify
|
|
490
|
+
|
|
491
|
+
- name: Generate ERD
|
|
492
|
+
run: erdify ./src/database --title "Database Schema" -o docs/erd.puml
|
|
493
|
+
|
|
494
|
+
- name: Generate PNG
|
|
495
|
+
run: |
|
|
496
|
+
sudo apt-get install -y plantuml
|
|
497
|
+
plantuml docs/erd.puml
|
|
498
|
+
|
|
499
|
+
- name: Commit changes
|
|
500
|
+
uses: stefanzweifel/git-auto-commit-action@v5
|
|
501
|
+
with:
|
|
502
|
+
commit_message: "docs: update ERD diagram"
|
|
503
|
+
file_pattern: "docs/erd.*"
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Integration with pre-commit hooks
|
|
507
|
+
|
|
508
|
+
Keep your ERD diagrams automatically updated on every commit using [pre-commit](https://pre-commit.com/):
|
|
509
|
+
|
|
510
|
+
```yaml
|
|
511
|
+
# .pre-commit-config.yaml
|
|
512
|
+
repos:
|
|
513
|
+
- repo: local
|
|
514
|
+
hooks:
|
|
515
|
+
- id: generate-erd
|
|
516
|
+
name: ποΈ Generate ERD Diagram
|
|
517
|
+
entry: erdify ./src/database --title "Database Schema" -o docs/erd.puml
|
|
518
|
+
language: system
|
|
519
|
+
files: ^src/database/.*\.py$
|
|
520
|
+
pass_filenames: false
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
Or using uvx (no installation required):
|
|
524
|
+
|
|
525
|
+
```yaml
|
|
526
|
+
# .pre-commit-config.yaml
|
|
527
|
+
repos:
|
|
528
|
+
- repo: local
|
|
529
|
+
hooks:
|
|
530
|
+
- id: generate-erd
|
|
531
|
+
name: ποΈ Generate ERD Diagram
|
|
532
|
+
entry: uvx erdify ./src/database --title "Database Schema" -o docs/erd.puml
|
|
533
|
+
language: system
|
|
534
|
+
files: ^src/database/.*\.py$
|
|
535
|
+
pass_filenames: false
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**Setup:**
|
|
539
|
+
|
|
540
|
+
```bash
|
|
541
|
+
# Install pre-commit
|
|
542
|
+
pip install pre-commit
|
|
543
|
+
|
|
544
|
+
# Install the hooks
|
|
545
|
+
pre-commit install
|
|
546
|
+
|
|
547
|
+
# Run manually on all files
|
|
548
|
+
pre-commit run generate-erd --all-files
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**How it works:**
|
|
552
|
+
- π Only triggers when files in `src/database/` change
|
|
553
|
+
- π Automatically regenerates `docs/erd.puml`
|
|
554
|
+
- β
Stages the updated diagram with your commit
|
|
555
|
+
- π« Fails if the diagram would change (ensuring docs stay in sync)
|
|
556
|
+
|
|
557
|
+
**Tip:** Add `docs/erd.puml` to your staged files before committing, or use the `--all-files` flag to regenerate.
|
|
558
|
+
|
|
559
|
+
## π Supported Features
|
|
560
|
+
|
|
561
|
+
| Feature | Status | Notes |
|
|
562
|
+
| -------- | -------- | ------- |
|
|
563
|
+
| Primary Keys | β
| `Field(primary_key=True)` |
|
|
564
|
+
| Foreign Keys | β
| `Field(foreign_key="table.column")` |
|
|
565
|
+
| Nullable Fields | β
| `str \| None` or `Optional[str]` |
|
|
566
|
+
| Default Values | β
| `Field(default=value)` |
|
|
567
|
+
| Indexes | β
| `Field(index=True)` |
|
|
568
|
+
| Enums | β
| Python `Enum` classes |
|
|
569
|
+
| Relationships | β
| `Relationship()` |
|
|
570
|
+
| Inheritance | β
| Mixin classes supported |
|
|
571
|
+
| Link Tables | β
| Many-to-many detection |
|
|
572
|
+
| Custom Table Names | β
| `__tablename__` attribute |
|
|
573
|
+
| Exclude Patterns | β
| `--exclude` glob on class/table name |
|
|
574
|
+
| Key Inference | β
| `--infer-keys` for Pydantic/dataclass (`id`, `<x>_id`) |
|
|
575
|
+
| SQLModel | β
| `Field()` / `Relationship()` |
|
|
576
|
+
| SQLAlchemy 2.0 | β
| `Mapped[...]` / `mapped_column()` |
|
|
577
|
+
| Pydantic | β
| `BaseModel` subclasses, nested refs as relationships |
|
|
578
|
+
| Dataclass | β
| `@dataclass`, nested refs as relationships |
|
|
579
|
+
|
|
580
|
+
## πΊοΈ Roadmap
|
|
581
|
+
|
|
582
|
+
Recently shipped:
|
|
583
|
+
|
|
584
|
+
| Feature | Status | Description |
|
|
585
|
+
| -------- | -------- | ------- |
|
|
586
|
+
| Exclude Option | β
Done | Exclude tables or entities from ERD generation using glob patterns |
|
|
587
|
+
| SQLAlchemy Support | β
Done | Native support for SQLAlchemy 2.0 (`Mapped` / `mapped_column`) models |
|
|
588
|
+
| Pydantic Support | β
Done | Generate ERDs from Pydantic models, with optional `--infer-keys` |
|
|
589
|
+
| Dataclass Support | β
Done | Support for standard Python dataclasses with type annotations |
|
|
590
|
+
|
|
591
|
+
Have a feature request? Please open an issue on [GitHub](https://github.com/devsuit-berlin/erdify/issues) to discuss it!
|
|
592
|
+
|
|
593
|
+
## π€ Contributing
|
|
594
|
+
|
|
595
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
596
|
+
|
|
597
|
+
## π Security
|
|
598
|
+
|
|
599
|
+
For security concerns, please see [SECURITY.md](SECURITY.md).
|
|
600
|
+
|
|
601
|
+
## π License
|
|
602
|
+
|
|
603
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
604
|
+
|
|
605
|
+
## π Acknowledgments
|
|
606
|
+
|
|
607
|
+
erdify stands on the shoulders of great open-source projects:
|
|
608
|
+
|
|
609
|
+
**Supported model frameworks**
|
|
610
|
+
|
|
611
|
+
- [SQLModel](https://sqlmodel.tiangolo.com/) - The awesome SQL database library
|
|
612
|
+
- [SQLAlchemy](https://www.sqlalchemy.org/) - The Python SQL toolkit and ORM
|
|
613
|
+
- [Pydantic](https://docs.pydantic.dev/) - Data validation using Python type hints
|
|
614
|
+
- [dataclasses](https://docs.python.org/3/library/dataclasses.html) - Python standard-library data classes
|
|
615
|
+
|
|
616
|
+
**Rendering & output**
|
|
617
|
+
|
|
618
|
+
- [PlantUML](https://plantuml.com/) - For the diagram rendering
|
|
619
|
+
- [Graphviz](https://graphviz.org/) - Layout engine behind PlantUML's ER diagrams
|
|
620
|
+
|
|
621
|
+
**Tooling & infrastructure**
|
|
622
|
+
|
|
623
|
+
- [uv](https://docs.astral.sh/uv/) - Packaging, builds and dependency management
|
|
624
|
+
- [Ruff](https://docs.astral.sh/ruff/) - Linting and formatting
|
|
625
|
+
- [mypy](https://mypy-lang.org/) - Static type checking
|
|
626
|
+
- [pytest](https://docs.pytest.org/) - Testing framework
|
|
627
|
+
- [pre-commit](https://pre-commit.com/) - Git hook management
|
|
628
|
+
|
|
629
|
+
**Community**
|
|
630
|
+
|
|
631
|
+
- [Contributor Covenant](https://www.contributor-covenant.org/) - Our Code of Conduct
|
|
632
|
+
- [Keep a Changelog](https://keepachangelog.com/) - Changelog format
|
|
633
|
+
- And everyone who [contributes](CONTRIBUTING.md) issues, ideas and pull requests π
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
Made with β€οΈ by [Devsuit GmbH](https://github.com/devsuit-berlin)
|