erdscope 0.2.0__tar.gz → 0.4.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.
- erdscope-0.4.0/PKG-INFO +416 -0
- erdscope-0.4.0/README.md +382 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/erd.py +2055 -507
- erdscope-0.4.0/erdscope.egg-info/PKG-INFO +416 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/erdscope.egg-info/SOURCES.txt +8 -1
- {erdscope-0.2.0 → erdscope-0.4.0}/pyproject.toml +3 -3
- erdscope-0.4.0/tests/test_build.py +45 -0
- erdscope-0.4.0/tests/test_characterization.py +1026 -0
- erdscope-0.4.0/tests/test_config_validation.py +475 -0
- erdscope-0.4.0/tests/test_db_integration.py +289 -0
- erdscope-0.4.0/tests/test_demo.py +164 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/tests/test_erd.py +304 -90
- erdscope-0.4.0/tests/test_merge_ir.py +929 -0
- erdscope-0.4.0/tests/test_pipeline.py +761 -0
- erdscope-0.2.0/PKG-INFO +0 -209
- erdscope-0.2.0/README.md +0 -175
- erdscope-0.2.0/erdscope.egg-info/PKG-INFO +0 -209
- {erdscope-0.2.0 → erdscope-0.4.0}/LICENSE +0 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/erdscope.egg-info/dependency_links.txt +0 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/erdscope.egg-info/entry_points.txt +0 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/erdscope.egg-info/requires.txt +0 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/erdscope.egg-info/top_level.txt +0 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/setup.cfg +0 -0
- {erdscope-0.2.0 → erdscope-0.4.0}/tests/test_e2e.py +0 -0
erdscope-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: erdscope
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Interactive, self-contained ER-diagram HTML and Excel table definitions from a MySQL, PostgreSQL, or SQLite database and/or application code (Rails / Prisma / Django) — single file, zero required dependencies
|
|
5
|
+
Author: tas6
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/orapli/erdscope
|
|
8
|
+
Project-URL: Documentation, https://orapli.github.io/erdscope/manual.html
|
|
9
|
+
Project-URL: Live demo, https://orapli.github.io/erdscope/
|
|
10
|
+
Project-URL: Repository, https://github.com/orapli/erdscope
|
|
11
|
+
Project-URL: Issues, https://github.com/orapli/erdscope/issues
|
|
12
|
+
Keywords: erd,er-diagram,database,schema,mysql,postgresql,postgres,sqlite,rails,prisma,django,documentation
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Database
|
|
18
|
+
Classifier: Topic :: Documentation
|
|
19
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Provides-Extra: mysql
|
|
24
|
+
Requires-Dist: PyMySQL; extra == "mysql"
|
|
25
|
+
Provides-Extra: postgres
|
|
26
|
+
Requires-Dist: psycopg[binary]; extra == "postgres"
|
|
27
|
+
Provides-Extra: yaml
|
|
28
|
+
Requires-Dist: PyYAML; extra == "yaml"
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: PyMySQL; extra == "all"
|
|
31
|
+
Requires-Dist: psycopg[binary]; extra == "all"
|
|
32
|
+
Requires-Dist: PyYAML; extra == "all"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# erdscope
|
|
36
|
+
|
|
37
|
+
[](https://github.com/orapli/erdscope/actions/workflows/ci.yml)
|
|
38
|
+
[](https://pypi.org/project/erdscope/)
|
|
39
|
+
|
|
40
|
+
Generate a **self-contained, interactive ER diagram** — and an **Excel table-definition
|
|
41
|
+
workbook** — from a live MySQL, PostgreSQL, or SQLite database, with a single-file,
|
|
42
|
+
zero-dependency Python CLI.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install erdscope
|
|
46
|
+
erdscope demo # no database of your own? try it right now — opens a sample diagram
|
|
47
|
+
|
|
48
|
+
erdscope mysql://readonly@127.0.0.1:3306/myapp_production -o erd.html
|
|
49
|
+
erdscope postgres://readonly@127.0.0.1:5432/myapp_production -o erd.html
|
|
50
|
+
erdscope sqlite:///path/to/app.db -o erd.html
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Three input sources — any one of them is enough** (a database is no longer required):
|
|
54
|
+
|
|
55
|
+
- **Database** (MySQL / PostgreSQL / SQLite) — the source of truth for tables, columns,
|
|
56
|
+
comments, indexes, and real foreign keys.
|
|
57
|
+
- **Application code** (`--models`: Rails / Prisma / Django) — adds association semantics
|
|
58
|
+
the database cannot express (`has_many :through`, polymorphic, ...), and can stand on
|
|
59
|
+
its own when there is no DB to point at.
|
|
60
|
+
- **Config file** (`tables:`) — declare or patch a schema by hand: add tables, columns,
|
|
61
|
+
indexes, and associations, or override and delete what the DB or code got wrong.
|
|
62
|
+
|
|
63
|
+
They merge in that order — **database → code → config**, each layer refining the previous
|
|
64
|
+
(the database wins on physical facts like column types; code and config win on
|
|
65
|
+
associations and logical names; config always has the final say). With no database URL,
|
|
66
|
+
nothing is connected and no password is prompted.
|
|
67
|
+
|
|
68
|
+
## Demo
|
|
69
|
+
|
|
70
|
+
**[Try the live demo →](https://orapli.github.io/erdscope/)** — a small e-commerce
|
|
71
|
+
schema with comments, indexes, real FKs and an inferred relation. Everything below
|
|
72
|
+
is one self-contained HTML file.
|
|
73
|
+
|
|
74
|
+
[](https://orapli.github.io/erdscope/)
|
|
75
|
+
|
|
76
|
+
Regenerate it anytime with `python3 docs/gen_demo.py`.
|
|
77
|
+
|
|
78
|
+
**[Read the user manual →](https://orapli.github.io/erdscope/manual.html)** — installation,
|
|
79
|
+
CLI/config reference, a full viewer guide, and troubleshooting. ([日本語版 →](https://orapli.github.io/erdscope/manual.ja.html))
|
|
80
|
+
|
|
81
|
+
## Install & usage
|
|
82
|
+
|
|
83
|
+
`pip install erdscope` (or `pipx install erdscope`) gives you the `erdscope` command.
|
|
84
|
+
Prefer not to install anything? `erd.py` is a single, dependency-free file — grab it
|
|
85
|
+
and run it with any Python 3.9+:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
curl -O https://raw.githubusercontent.com/orapli/erdscope/main/erd.py
|
|
89
|
+
python3 erd.py ... # identical to the erdscope command below
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
No database of your own to point it at yet? `erdscope demo` builds a small sample
|
|
93
|
+
e-commerce SQLite database in a temp directory, generates the diagram, and opens it in
|
|
94
|
+
your browser — nothing to download or set up:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
erdscope demo
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
It's a normal run under the hood, so every other flag still applies
|
|
101
|
+
(`erdscope demo --excel defs.xlsx`, `erdscope demo --only 'order*'`, ...); add
|
|
102
|
+
`--no-open` to skip launching the browser.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
erdscope mysql://readonly@127.0.0.1:3306/myapp_production -o erd.html
|
|
106
|
+
|
|
107
|
+
# PostgreSQL: same thing (schema defaults to public; override with ?schema=name)
|
|
108
|
+
erdscope postgres://readonly@127.0.0.1:5432/myapp_production -o erd.html
|
|
109
|
+
|
|
110
|
+
# SQLite: just point at the file (no server, nothing to install — uses stdlib sqlite3)
|
|
111
|
+
erdscope sqlite:///path/to/app.db -o erd.html
|
|
112
|
+
|
|
113
|
+
# enrich with association semantics parsed from application code (optional)
|
|
114
|
+
erdscope mysql://readonly@127.0.0.1:3306/myapp_production \
|
|
115
|
+
--models /path/to/rails/app -o erd.html
|
|
116
|
+
|
|
117
|
+
# also write a table-definition workbook
|
|
118
|
+
erdscope mysql://readonly@127.0.0.1:3306/myapp_production \
|
|
119
|
+
--excel table_definitions.xlsx -o erd.html
|
|
120
|
+
|
|
121
|
+
# no database — generate straight from application code
|
|
122
|
+
erdscope --models /path/to/rails/app -o erd.html
|
|
123
|
+
|
|
124
|
+
# no database — generate from a hand-written config schema (see Config file below)
|
|
125
|
+
erdscope --config schema.yml -o erd.html
|
|
126
|
+
|
|
127
|
+
# multiple code sources merge in order, later wins (--models is repeatable)
|
|
128
|
+
erdscope --models /path/to/rails/app --models /path/to/schema.prisma -o erd.html
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Cloned the repository instead of installing from PyPI? A ready-to-run SQLite sample
|
|
132
|
+
(the same schema `erdscope demo` uses) also ships committed in
|
|
133
|
+
[`examples/`](examples/) — `python3 erd.py sqlite:///examples/demo_shop.db -o shop.html`.
|
|
134
|
+
See [examples/README.md](examples/README.md).
|
|
135
|
+
|
|
136
|
+
Behind a bastion? Open an SSH tunnel first and point at localhost:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
ssh -N -L 3307:db-host:3306 bastion &
|
|
140
|
+
erdscope mysql://readonly@127.0.0.1:3307/myapp_production -o erd.html
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Use a read-only account, and leave the password out of the URL — if it's not in
|
|
144
|
+
`MYSQL_PWD` either, you'll be prompted for it (hidden input, never touches argv or
|
|
145
|
+
shell history). See [Dependencies](#dependencies) below for how the DB connection
|
|
146
|
+
itself is made.
|
|
147
|
+
|
|
148
|
+
### Options
|
|
149
|
+
|
|
150
|
+
| Option | Description |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `demo` | Positional value: generate from a bundled sample database instead of a real one — no database of your own needed. Every other flag below still applies |
|
|
153
|
+
| `-o FILE` | Output HTML path (default: `erd.html`; `erd_demo.html` for `demo`, so it never overwrites a real run's output) |
|
|
154
|
+
| `--models PATH` | Merge associations parsed from code: a Rails project (or `app/models` dir), a `schema.prisma`, or a Django project — auto-detected. **Repeatable** — multiple sources merge in the order given (later wins). Usable with no database URL |
|
|
155
|
+
| `--excel FILE.xlsx` | Also write a table-definition workbook: an overview sheet plus one sheet per table (columns, defaults, keys, comments, indexes, associations) |
|
|
156
|
+
| `--excel-template FILE.xlsx` | Override the workbook's colors/fonts/borders from a template `.xlsx` — see `excel-template.xlsx` and its `Styles` sheet for the 5-cell contract (default: built-in styling) |
|
|
157
|
+
| `--max-rows N` | Max column rows shown per table (default: 15; the rest scroll) |
|
|
158
|
+
| `--only 'user*,post*'` | Include only tables matching the glob pattern(s) |
|
|
159
|
+
| `--exclude '*_logs'` | Exclude tables matching the glob pattern(s) |
|
|
160
|
+
| `--infer-fk` | Guess relations from `*_id` column names when no real association/FK backs them (off by default — see below) |
|
|
161
|
+
| `--table-map 'Widget=crm_widgets'` | Rails only: override a model's table when static analysis can't determine it (e.g. `table_name` set inside a concern that lives in a gem, not the app). Repeatable; comma-separated lists accepted |
|
|
162
|
+
| `--config PATH` | Load defaults from a config file instead of repeating flags — see below. Auto-discovered as `.erdscope.json`/`.yml`/`.yaml` in the current directory if not given |
|
|
163
|
+
| `--no-config` | Skip config auto-discovery even if `.erdscope.*` exists in the cwd |
|
|
164
|
+
| `--no-open` | Skip automatically opening a browser after generating. Only relevant to `demo` (which opens one by default); accepted but has no effect on a normal run |
|
|
165
|
+
|
|
166
|
+
## Config file
|
|
167
|
+
|
|
168
|
+
Once the flag list above gets long, put it in a config file instead — `.erdscope.json`
|
|
169
|
+
(or `.yml`/`.yaml` with PyYAML) next to where you run the tool is picked up automatically.
|
|
170
|
+
Most keys mirror a CLI option (an explicit flag always wins over the config); the DB
|
|
171
|
+
connection is config-only as `engine`/`host`/`port`/`user`/`database` — deliberately with
|
|
172
|
+
no password field — and `relations` manually declares relations no FK, code, or inference
|
|
173
|
+
can find. `engine` can also be `"sqlite"`, in which case `database` is a local file path
|
|
174
|
+
(not a database name) and `host`/`port`/`user` don't apply. See the [Config file chapter of the manual](https://orapli.github.io/erdscope/manual.html#config-file) for the full key
|
|
175
|
+
list and semantics, and [`erdscope.example.yml`](erdscope.example.yml) for a fully
|
|
176
|
+
annotated sample based on the live demo's schema.
|
|
177
|
+
|
|
178
|
+
### Config as a schema source
|
|
179
|
+
|
|
180
|
+
Beyond settings, the config can carry a **`tables:`** section that is itself a full input
|
|
181
|
+
source — enough to generate a diagram with no database and no code at all, or to patch
|
|
182
|
+
what the other sources produce. It merges as the highest-priority layer, so it can add
|
|
183
|
+
new tables/columns/indexes/associations, override attributes, or delete them:
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
title: billing
|
|
187
|
+
tables:
|
|
188
|
+
customers:
|
|
189
|
+
comment: Customer accounts
|
|
190
|
+
primary_key: id
|
|
191
|
+
columns:
|
|
192
|
+
- { name: id, type: bigint, primary: true }
|
|
193
|
+
- { name: email, type: varchar, nullable: false, comment: Login address }
|
|
194
|
+
associations:
|
|
195
|
+
- { type: has_many, name: invoices, target: invoices }
|
|
196
|
+
invoices:
|
|
197
|
+
columns:
|
|
198
|
+
- { name: id, type: bigint, primary: true }
|
|
199
|
+
- { name: customer_id, type: bigint }
|
|
200
|
+
associations:
|
|
201
|
+
- { type: belongs_to, name: customer, target: customers, foreign_key: customer_id }
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Patch an existing DB/code result instead of declaring from scratch — override a column,
|
|
205
|
+
delete a stale one, drop a whole table, or replace a table's column list wholesale:
|
|
206
|
+
|
|
207
|
+
```yaml
|
|
208
|
+
tables:
|
|
209
|
+
orders:
|
|
210
|
+
columns:
|
|
211
|
+
- { name: status, comment: Order status } # override just this attribute
|
|
212
|
+
- { name: legacy_flag, drop: true } # delete a column
|
|
213
|
+
temp_scratch:
|
|
214
|
+
drop: true # delete a whole table
|
|
215
|
+
reports:
|
|
216
|
+
columns_mode: replace # discard lower-layer columns first
|
|
217
|
+
columns:
|
|
218
|
+
- { name: id, type: bigint, primary: true }
|
|
219
|
+
- { name: body, type: text }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Config is validated in two passes: **syntactically at load** (unknown keys, bad types,
|
|
223
|
+
malformed operations) and **semantically at run time** (a `drop`/reference must point at
|
|
224
|
+
something that actually exists once all sources are merged) — typos never pass silently.
|
|
225
|
+
The full `tables:` schema, the `drop`/`replace` operations, and the precedence rules are
|
|
226
|
+
documented in the [manual](https://orapli.github.io/erdscope/manual.html#config-file).
|
|
227
|
+
|
|
228
|
+
## Dependencies
|
|
229
|
+
|
|
230
|
+
`erd.py` runs with **zero required dependencies** — everything below is optional, and
|
|
231
|
+
the tool degrades gracefully (falls back, or fails with a clear message) when a piece
|
|
232
|
+
is missing. If you installed via pip, extras pull them in for you:
|
|
233
|
+
`pip install 'erdscope[mysql]'` (PyMySQL), `'erdscope[postgres]'` (psycopg),
|
|
234
|
+
`'erdscope[yaml]'` (PyYAML), or `'erdscope[all]'`.
|
|
235
|
+
|
|
236
|
+
| Library | Used for | If not installed |
|
|
237
|
+
|---|---|---|
|
|
238
|
+
| [PyMySQL](https://pypi.org/project/PyMySQL/) | MySQL connections | Falls back to shelling out to the `mysql` CLI (must be on `PATH`) |
|
|
239
|
+
| [psycopg](https://pypi.org/project/psycopg/) (or psycopg2) | PostgreSQL connections | Falls back to shelling out to the `psql` CLI (must be on `PATH`) |
|
|
240
|
+
| _(none)_ | **SQLite** connections (`sqlite:///file.db`) | Uses Python's built-in `sqlite3` — always available, nothing to install or fall back to |
|
|
241
|
+
| [PyYAML](https://pypi.org/project/PyYAML/) | Reading a `.yml`/`.yaml` config file | A `.json` config still works with no dependency; pointing `--config`/auto-discovery at a `.yml`/`.yaml` file without PyYAML installed exits with a clear error |
|
|
242
|
+
|
|
243
|
+
Excel output (`--excel`) needs none of these — it's written directly via the stdlib
|
|
244
|
+
`zipfile`/XML, not a spreadsheet library.
|
|
245
|
+
|
|
246
|
+
Test-only, and only if you run that particular suite:
|
|
247
|
+
|
|
248
|
+
| Library | Used for |
|
|
249
|
+
|---|---|
|
|
250
|
+
| [openpyxl](https://pypi.org/project/openpyxl/) | Roundtrip-verifying `--excel` output in the unit tests (`tests/test_erd.py`); that one test skips itself if it's missing |
|
|
251
|
+
| [Playwright](https://playwright.dev/python/) | The browser E2E suite (`tests/test_e2e.py`) — see [Tests](#tests) below |
|
|
252
|
+
|
|
253
|
+
## What you get
|
|
254
|
+
|
|
255
|
+
Feature highlights — each link goes to the relevant [manual](https://orapli.github.io/erdscope/manual.html) chapter:
|
|
256
|
+
|
|
257
|
+
- **Database truth** — tables, columns (full SQL types, defaults, extras), table and
|
|
258
|
+
column comments, indexes, and real FK constraints, read from the database catalog
|
|
259
|
+
(`information_schema` on MySQL, `pg_catalog` on PostgreSQL)
|
|
260
|
+
- **Code semantics on top** — `--models` merges Rails / Prisma / Django associations;
|
|
261
|
+
declared, DB-FK, and inferred edges stay [visually distinct](https://orapli.github.io/erdscope/manual.html#viewer-edges)
|
|
262
|
+
- **[Interactive exploration](https://orapli.github.io/erdscope/manual.html#viewer-guide)** — focus with depth and dependency
|
|
263
|
+
direction, two-level hiding, table *and column* search (with regex/case toggles), a
|
|
264
|
+
non-filtering Highlight search that survives into exports, named views, share links
|
|
265
|
+
- **[Readable layouts](https://orapli.github.io/erdscope/manual.html#viewer-layout)** — viewport-aware packing with crossing
|
|
266
|
+
reduction, drag-to-snap with guide lines, multi-select align/distribute, Auto-tidy,
|
|
267
|
+
layout undo/redo
|
|
268
|
+
- **[Exports](https://orapli.github.io/erdscope/manual.html#exports)** — PNG (2x), SVG, Mermaid, and PlantUML, each with its own
|
|
269
|
+
copy and download buttons and image options, plus the Excel workbook
|
|
270
|
+
(customizable via `--excel-template`)
|
|
271
|
+
- **[Logical names](https://orapli.github.io/erdscope/manual.html#viewer-names)** — a table's DB comment doubles as a searchable
|
|
272
|
+
logical name (e.g. `users(Customer accounts)`), with independent display modes for
|
|
273
|
+
the live view and exports
|
|
274
|
+
- **Extras** — a built-in `?` shortcuts/help popup, dark mode, print stylesheet,
|
|
275
|
+
resizable/collapsible panes
|
|
276
|
+
|
|
277
|
+
## Tests
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
python3 -m unittest discover -s tests -v
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The IR builders and the Excel writer are covered by pure unit tests; the overlay
|
|
284
|
+
parsers have minimal fixtures under `tests/fixture_*`. No database is required to
|
|
285
|
+
run the tests.
|
|
286
|
+
|
|
287
|
+
`tests/test_e2e.py` drives the generated HTML's client-side JS (grid layout,
|
|
288
|
+
multi-select align/distribute, drag-to-snap, Auto-tidy) in a real headless browser. It's optional and skips
|
|
289
|
+
itself if not set up:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
pip install playwright && playwright install chromium
|
|
293
|
+
python3 -m unittest tests.test_e2e -v
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Performance
|
|
297
|
+
|
|
298
|
+
Measured against synthetic SQLite schemas (`benchmarks/gen_schema.py`, ~2 FK edges per
|
|
299
|
+
table) at three sizes:
|
|
300
|
+
|
|
301
|
+
| Tables | Edges | CLI gen (median) | HTML size | Initial paint (median) | Re-layout (median) | Python RSS | JS heap |
|
|
302
|
+
|---|---|---|---|---|---|---|---|
|
|
303
|
+
| 100 | 190 | 0.05 s | 292 KB | 91 ms | 37 ms | 37 MB | 2.0 MB |
|
|
304
|
+
| 300 | 592 | 0.06 s | 533 KB | 206 ms | 131 ms | 42 MB | 1.9 MB |
|
|
305
|
+
| 1,000 | 2,015 | 0.11 s | 1.35 MB | 1.18 s | 1.04 s | 59 MB | 3.2 MB |
|
|
306
|
+
|
|
307
|
+
Notes: measured in a sandboxed Linux environment against headless Chromium
|
|
308
|
+
(Playwright), not dedicated hardware — treat these as relative/order-of-magnitude
|
|
309
|
+
numbers rather than absolute guarantees for your machine. "CLI gen" and the browser
|
|
310
|
+
timings are each the median of 3 runs; Python RSS/JS heap are single best-effort
|
|
311
|
+
samples. Generation itself (`erd.py sqlite:///... -o out.html`) stays fast at every
|
|
312
|
+
size tested — SQLite parsing isn't the bottleneck.
|
|
313
|
+
|
|
314
|
+
**Recommendation:** the constraint at scale is the browser-side interactive
|
|
315
|
+
re-layout, not generation. Linear interpolation between the 300- and 1,000-table
|
|
316
|
+
measurements above puts the 1-second crossing at roughly 970 tables; we round that
|
|
317
|
+
down to **~900 tables** as a deliberately conservative rule of thumb, since these
|
|
318
|
+
are single-environment numbers (initial paint itself stays comfortably under 3
|
|
319
|
+
seconds through 1,000 tables). Past that rough threshold, narrow the diagram with
|
|
320
|
+
`--only`/`--exclude` instead of
|
|
321
|
+
rendering the whole schema at once — see the [manual's Large
|
|
322
|
+
schemas section](https://orapli.github.io/erdscope/manual.html#large-schemas) for
|
|
323
|
+
details.
|
|
324
|
+
|
|
325
|
+
Reproduce or re-run at other sizes with:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
python3 benchmarks/gen_schema.py --tables 300 --out /tmp/bench_300.db
|
|
329
|
+
python3 benchmarks/run_bench.py --tables 100,300,1000 # generates, benchmarks, prints JSON + a summary table
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`run_bench.py` is a manual script (not part of the `unittest` suite) — see its
|
|
333
|
+
docstring for the full methodology and the Playwright venv it expects.
|
|
334
|
+
|
|
335
|
+
## Extending
|
|
336
|
+
|
|
337
|
+
Everything downstream (UI, layouts, exports) consumes the intermediate representation
|
|
338
|
+
documented at the top of `erd.py`. Both input layers are pluggable: a **database
|
|
339
|
+
adapter** turns a URL scheme into that shape, and a **framework overlay** turns an
|
|
340
|
+
application-code project into it. Each is a small class registered under the scheme /
|
|
341
|
+
project kind it handles, so adding one never touches the dispatch code.
|
|
342
|
+
|
|
343
|
+
### Custom adapters and overlays (a plugin file)
|
|
344
|
+
|
|
345
|
+
Write a plain Python file that subclasses the base and registers itself, then load it
|
|
346
|
+
with `--adapter path/to/plugin.py` (or config `adapters: [...]`). No need to rebuild
|
|
347
|
+
`erd.py` — the plugin registers into the running process, and a plugin works the same
|
|
348
|
+
against the single-file `erd.py` or a `pip install erdscope`.
|
|
349
|
+
|
|
350
|
+
```python
|
|
351
|
+
# my_sqlite.py — a custom database adapter
|
|
352
|
+
from erd import DBAdapter, register_adapter, mysql_ir
|
|
353
|
+
|
|
354
|
+
@register_adapter
|
|
355
|
+
class SqliteAdapter(DBAdapter):
|
|
356
|
+
schemes = ('sqlite',) # the URL scheme(s) it answers to
|
|
357
|
+
name = 'sqlite' # provider id recorded in the output
|
|
358
|
+
label = 'SQLite' # pretty name for the progress line
|
|
359
|
+
|
|
360
|
+
def fetch(self, url):
|
|
361
|
+
# ...read the schema for `url` and return the IR (the `tables` dict).
|
|
362
|
+
# mysql_ir() builds it from information_schema-shaped rows for you.
|
|
363
|
+
return mysql_ir(table_rows, col_rows, fk_rows, index_rows)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
python3 erd.py sqlite:///app.db --adapter my_sqlite.py -o erd.html
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
A **framework overlay** is the same idea against a `--models` path:
|
|
371
|
+
|
|
372
|
+
```python
|
|
373
|
+
# my_framework.py
|
|
374
|
+
from erd import FrameworkOverlay, register_overlay, make_provider_result
|
|
375
|
+
|
|
376
|
+
@register_overlay
|
|
377
|
+
class SequelizeOverlay(FrameworkOverlay):
|
|
378
|
+
name = 'sequelize'
|
|
379
|
+
priority = 5 # lower detect()s first; first match wins
|
|
380
|
+
|
|
381
|
+
def detect(self, root):
|
|
382
|
+
return root.is_dir() and (root / 'models' / 'index.js').exists()
|
|
383
|
+
|
|
384
|
+
def build(self, root, table_map):
|
|
385
|
+
tables = ... # parse `root` into the IR
|
|
386
|
+
return make_provider_result('framework', 'sequelize', tables, location=str(root))
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The built-in `db/mysql.py`, `db/postgres.py` and `frameworks/{rails,prisma,django}.py`
|
|
390
|
+
are the working examples of both patterns.
|
|
391
|
+
|
|
392
|
+
### Building `erd.py`
|
|
393
|
+
|
|
394
|
+
The shipped `erd.py` is a **build artifact**: the development source lives under
|
|
395
|
+
`src/erdscope/`. The Python is organised into concern-named fragments (`ir.py`,
|
|
396
|
+
`merge.py`, `providers.py`, `exporters.py`, `config.py`, `cli.py`, …) plus two
|
|
397
|
+
self-assembling **folders** — `db/` (the DB adapters) and `frameworks/` (the overlays)
|
|
398
|
+
— whose files are all included automatically (`base.py` first, then sorted), so adding a
|
|
399
|
+
built-in adapter or overlay is just dropping a new file in the folder. They are
|
|
400
|
+
concatenated in order, and the ~3,600-line embedded viewer (HTML/CSS/JS) lives in
|
|
401
|
+
`viewer.html`, out of the Python. Edit the relevant file, then regenerate the single file:
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
python3 tools/build_single_file.py # rewrites erd.py from the source
|
|
405
|
+
python3 tools/build_single_file.py --check # CI-style check that erd.py is in sync
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The fragments are an amalgamation of one flat module (SQLite-style) — no cross-module
|
|
409
|
+
imports — and the viewer is inlined into a one-line sentinel, so the whole build is pure
|
|
410
|
+
textual assembly and `erd.py` stays a self-contained, zero-dependency single file:
|
|
411
|
+
grabbing and running it, or `pip install erdscope`, is unchanged. CI runs the `--check`
|
|
412
|
+
above, so a hand-edit of `erd.py` or a forgotten rebuild fails the build.
|
|
413
|
+
|
|
414
|
+
## License
|
|
415
|
+
|
|
416
|
+
MIT
|