erdscope 0.4.1__tar.gz → 0.6.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.1/erdscope.egg-info → erdscope-0.6.0}/PKG-INFO +176 -8
- {erdscope-0.4.1 → erdscope-0.6.0}/README.md +175 -7
- {erdscope-0.4.1 → erdscope-0.6.0}/erd.py +1808 -101
- {erdscope-0.4.1 → erdscope-0.6.0/erdscope.egg-info}/PKG-INFO +176 -8
- {erdscope-0.4.1 → erdscope-0.6.0}/erdscope.egg-info/SOURCES.txt +5 -1
- {erdscope-0.4.1 → erdscope-0.6.0}/pyproject.toml +1 -1
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_config_validation.py +481 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_e2e.py +521 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_erd.py +116 -0
- erdscope-0.6.0/tests/test_groups.py +325 -0
- erdscope-0.6.0/tests/test_notes.py +556 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_pipeline.py +188 -0
- erdscope-0.6.0/tests/test_provider_contract.py +221 -0
- erdscope-0.6.0/tests/test_rails_schema.py +900 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/LICENSE +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/erdscope.egg-info/dependency_links.txt +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/erdscope.egg-info/entry_points.txt +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/erdscope.egg-info/requires.txt +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/erdscope.egg-info/top_level.txt +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/setup.cfg +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_build.py +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_characterization.py +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_db_integration.py +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_demo.py +0 -0
- {erdscope-0.4.1 → erdscope-0.6.0}/tests/test_merge_ir.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: erdscope
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
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
5
|
Author: tas6
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,9 +37,15 @@ Dynamic: license-file
|
|
|
37
37
|
[](https://github.com/orapli/erdscope/actions/workflows/ci.yml)
|
|
38
38
|
[](https://pypi.org/project/erdscope/)
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
**Interactive ER diagrams and documented schema definitions.** Generate a
|
|
41
|
+
**self-contained, interactive ER diagram** — and an **Excel table-definition
|
|
41
42
|
workbook** — from a live MySQL, PostgreSQL, or SQLite database, with a single-file,
|
|
42
|
-
zero-dependency Python CLI.
|
|
43
|
+
zero-dependency Python CLI. Turn an ER diagram into a documented schema: attach design
|
|
44
|
+
decisions, operational rules, and ADR links to tables and relationships with config
|
|
45
|
+
[`notes:`](#notes-attach-design-decisions-to-the-diagram) — plain text and http(s) links
|
|
46
|
+
only, validated against the real schema so a note can never point at something that
|
|
47
|
+
doesn't exist — and draw a rounded, titled frame around a set of related tables with config
|
|
48
|
+
[`groups:`](#groups-draw-a-frame-around-related-tables).
|
|
43
49
|
|
|
44
50
|
```bash
|
|
45
51
|
pip install erdscope
|
|
@@ -54,9 +60,10 @@ erdscope sqlite:///path/to/app.db -o erd.html
|
|
|
54
60
|
|
|
55
61
|
- **Database** (MySQL / PostgreSQL / SQLite) — the source of truth for tables, columns,
|
|
56
62
|
comments, indexes, and real foreign keys.
|
|
57
|
-
- **Application code** (`--models`: Rails / Prisma / Django
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
- **Application code** (`--models`: Rails / Prisma / Django, or a Rails `db/schema.rb` —
|
|
64
|
+
see [Typed input sources](#typed-input-sources-sources) below) — adds association
|
|
65
|
+
semantics the database cannot express (`has_many :through`, polymorphic, ...), and can
|
|
66
|
+
stand on its own when there is no DB to point at.
|
|
60
67
|
- **Config file** (`tables:`) — declare or patch a schema by hand: add tables, columns,
|
|
61
68
|
indexes, and associations, or override and delete what the DB or code got wrong.
|
|
62
69
|
|
|
@@ -225,6 +232,138 @@ something that actually exists once all sources are merged) — typos never pass
|
|
|
225
232
|
The full `tables:` schema, the `drop`/`replace` operations, and the precedence rules are
|
|
226
233
|
documented in the [manual](https://orapli.github.io/erdscope/manual.html#config-file).
|
|
227
234
|
|
|
235
|
+
### Typed input sources (`sources:`)
|
|
236
|
+
|
|
237
|
+
`--models`/config `models` auto-detect what kind of project a path is. The config-only
|
|
238
|
+
**`sources:`** list is the typed alternative: each entry names its own `type`, so nothing
|
|
239
|
+
needs detecting and several inputs can be declared explicitly and unambiguously —
|
|
240
|
+
including **`rails.schema`**, which statically parses a Rails `db/schema.rb` file
|
|
241
|
+
(columns, indexes, real foreign keys) with **no live database and no Ruby execution**:
|
|
242
|
+
|
|
243
|
+
```yaml
|
|
244
|
+
version: 1
|
|
245
|
+
sources:
|
|
246
|
+
- id: schema
|
|
247
|
+
type: rails.schema
|
|
248
|
+
path: db/schema.rb
|
|
249
|
+
- id: app
|
|
250
|
+
type: rails.models # every registered overlay gets its own <name>.models type
|
|
251
|
+
path: app/models
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
(The optional top-level `version: 1` is a config-format marker — currently the only
|
|
255
|
+
supported value, with no other effect; it's for future config-format changes to key off.)
|
|
256
|
+
|
|
257
|
+
A typed source that parses **nothing** — say, a Prisma project accidentally declared as
|
|
258
|
+
`rails.models` — is a hard error naming the source id and the layout the type expected,
|
|
259
|
+
never a silently empty diagram. If an empty result is genuinely intended (a scaffolded
|
|
260
|
+
but still-empty `app/models`, for example), opt in per source with `allow_empty: true`;
|
|
261
|
+
a `rails.project` entry passes the flag down to both of its expanded halves.
|
|
262
|
+
|
|
263
|
+
A **`rails.project`** entry is a macro for a whole Rails app root: it expands to both the
|
|
264
|
+
`rails.schema` (`<root>/db/schema.rb`) and `rails.models` (`<root>/app/models`) halves,
|
|
265
|
+
whichever exist —
|
|
266
|
+
|
|
267
|
+
```yaml
|
|
268
|
+
sources:
|
|
269
|
+
- id: app
|
|
270
|
+
type: rails.project
|
|
271
|
+
path: ../myapp
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
is equivalent to declaring both `rails.schema` and `rails.models` entries above. A
|
|
275
|
+
`schema.rb`-derived foreign key merges as a **schema FK** (a teal badge in the viewer,
|
|
276
|
+
distinct from a live `DB FK`), and the merge authority order extends to
|
|
277
|
+
**Config > live DB > rails.schema > models** (physical facts) — a `schema.rb` dump is
|
|
278
|
+
closer to the real database than code is, but a live DB read still wins when both are
|
|
279
|
+
present. See the [Input sources chapter of the manual](https://orapli.github.io/erdscope/manual.html#input-sources)
|
|
280
|
+
for the full `sources[]` reference.
|
|
281
|
+
|
|
282
|
+
### Notes: attach design decisions to the diagram
|
|
283
|
+
|
|
284
|
+
Config **`notes:`** attaches short, plain-text write-ups — design decisions, operational
|
|
285
|
+
rules, ADR links — to a table, a specific relation, or the whole diagram. Notes never
|
|
286
|
+
touch the schema itself (no effect on columns, associations, or merge precedence); they're
|
|
287
|
+
a read-only sidecar validated against the final merged schema and rendered next to it:
|
|
288
|
+
|
|
289
|
+
```yaml
|
|
290
|
+
notes:
|
|
291
|
+
- id: user-retention
|
|
292
|
+
target: { type: table, table: users }
|
|
293
|
+
title: Retention policy
|
|
294
|
+
text: Suspended accounts are kept for 1 year, then anonymized.
|
|
295
|
+
links:
|
|
296
|
+
- { label: ADR-004, url: https://example.com/adr/004 }
|
|
297
|
+
|
|
298
|
+
- id: order-ownership
|
|
299
|
+
target: { type: relation, source_table: orders, target_table: users, foreign_key: user_id }
|
|
300
|
+
text: Orders are kept after a user is anonymized (financial record-keeping).
|
|
301
|
+
|
|
302
|
+
- id: diagram-conventions
|
|
303
|
+
target: { type: global }
|
|
304
|
+
title: How to read this diagram
|
|
305
|
+
text: The dotted amber edge is an inferred relation, not a real FK.
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- Every note needs a config-unique `id` and non-empty `text`; `title` and `links` are
|
|
309
|
+
optional. Link `url`s must be `http://` or `https://` — anything else (`javascript:`,
|
|
310
|
+
`data:`, a bare string) is rejected at load time.
|
|
311
|
+
- `target.type` is `table`, `relation`, or `global`. A `relation` note is identified by
|
|
312
|
+
`source_table` (the side that **holds** the association — the `belongs_to`/FK-holding
|
|
313
|
+
side for a `belongs_to`, the owning side for a `has_many`) and `target_table`;
|
|
314
|
+
`foreign_key`/`name`/`assoc_type`/`through`/`polymorphic` are optional narrowing keys for
|
|
315
|
+
when a table has more than one relation to the same target (`assoc_type` is the
|
|
316
|
+
association kind — `has_many`/`belongs_to`/`has_one`/`has_and_belongs_to_many` — which
|
|
317
|
+
tells apart, say, a `has_many` and a `has_one` that share a name and target). A note that
|
|
318
|
+
matches no relation, or more than one, is a hard error naming the note's `id` — never a
|
|
319
|
+
silent guess.
|
|
320
|
+
- Validation runs **twice**, like `tables:` above: syntax at load time, then semantically
|
|
321
|
+
against the schema that results **after** DB/`--models`/config `tables:` are all merged
|
|
322
|
+
— so a note can reference a table or relation that config itself adds, and a note on
|
|
323
|
+
something config *removes* is correctly an error.
|
|
324
|
+
- Rendered as plain, HTML-escaped text only — no Markdown, no raw HTML, no scripts — in
|
|
325
|
+
the table's detail panel, next to the matching relation, or in the diagram's legend for
|
|
326
|
+
a `global` note. Notes are also searchable (title, text, link labels). A note whose
|
|
327
|
+
table is hidden from the current view simply doesn't appear in the (now absent) detail
|
|
328
|
+
panel; a `global` note's legend entry is unaffected.
|
|
329
|
+
- `write_excel` accepts the same notes but doesn't use them yet in this release — an Excel
|
|
330
|
+
Notes sheet is a possible future addition, not implemented today.
|
|
331
|
+
|
|
332
|
+
See the [Notes chapter of the manual](https://orapli.github.io/erdscope/manual.html#notes)
|
|
333
|
+
for the full reference.
|
|
334
|
+
|
|
335
|
+
### Groups: draw a frame around related tables
|
|
336
|
+
|
|
337
|
+
Config **`groups:`** draws a rounded frame with a title around a set of related tables in
|
|
338
|
+
the diagram — a lightweight way to call out a domain ("Billing", "Orders") without changing
|
|
339
|
+
the schema or the layout. Like `notes:`, it's a read-only sidecar validated against the
|
|
340
|
+
final merged schema:
|
|
341
|
+
|
|
342
|
+
```yaml
|
|
343
|
+
groups:
|
|
344
|
+
- id: billing
|
|
345
|
+
title: Billing
|
|
346
|
+
tables: [invoices, payments, coupons]
|
|
347
|
+
color: "#0d9488"
|
|
348
|
+
|
|
349
|
+
- id: catalog
|
|
350
|
+
tables: [products, categories, product_categories]
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
- Every group needs a config-unique `id` and non-empty `tables`; `title` (defaults to `id`)
|
|
354
|
+
and `color` (a hex string, e.g. `#0d9488`) are optional.
|
|
355
|
+
- A table may belong to **at most one** group — claiming the same table from two groups is
|
|
356
|
+
a hard error naming both group `id`s and the table, not a silently-picked winner. There's
|
|
357
|
+
no support for overlapping/nested groups in this release.
|
|
358
|
+
- Validation runs twice, like `notes:`/`tables:` above: syntax at load time, then
|
|
359
|
+
semantically against the final merged schema, so a group can reference a table config
|
|
360
|
+
itself adds, and a group naming something config *removes* is correctly an error.
|
|
361
|
+
- Purely visual: groups never affect layout, merge precedence, or associations — the frame
|
|
362
|
+
is drawn around wherever its member tables already ended up. In the viewer, drag a
|
|
363
|
+
group's title to move every member together; a "Groups" toolbar toggle shows/hides the
|
|
364
|
+
frames, and both PNG and SVG exports include them. `--only`/`--exclude` narrow a group's
|
|
365
|
+
membership down to the surviving tables, dropping the group entirely if none remain.
|
|
366
|
+
|
|
228
367
|
## Dependencies
|
|
229
368
|
|
|
230
369
|
`erd.py` runs with **zero required dependencies** — everything below is optional, and
|
|
@@ -250,6 +389,24 @@ Test-only, and only if you run that particular suite:
|
|
|
250
389
|
| [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
390
|
| [Playwright](https://playwright.dev/python/) | The browser E2E suite (`tests/test_e2e.py`) — see [Tests](#tests) below |
|
|
252
391
|
|
|
392
|
+
## Verified versions
|
|
393
|
+
|
|
394
|
+
The input formats erdscope parses change rarely, so exact versions matter less than
|
|
395
|
+
they might seem — newer releases are expected to keep working, though constructs
|
|
396
|
+
beyond the tested ones may be ignored rather than parsed. For the record, this is what
|
|
397
|
+
each input is actually verified against:
|
|
398
|
+
|
|
399
|
+
| Input | Verified against |
|
|
400
|
+
|---|---|
|
|
401
|
+
| MySQL | **8.4** — real-server integration tests in CI (`information_schema`); developed against 8.x |
|
|
402
|
+
| PostgreSQL | **16** — real-server integration tests in CI (`pg_catalog`/`information_schema`) |
|
|
403
|
+
| SQLite | the `sqlite3` module bundled with CPython (any supported 3.x) |
|
|
404
|
+
| Rails `schema.rb` | the format Rails **7.x / 8.x** writes (`ActiveRecord::Schema[7.x]`, and the classic un-versioned header) |
|
|
405
|
+
| Rails models | the association DSL as of Rails **7.x** — `has_many`/`has_one`/`belongs_to`/`has_and_belongs_to_many`, `through:`, `polymorphic:`, STI, concerns, custom base classes; also exercised against Mastodon's real codebase. Dynamically computed definitions (and `structure.sql`) are out of scope |
|
|
406
|
+
| Prisma | the schema language as of Prisma **5 / 6** — `@map`/`@@map`, enums, named relations, implicit and explicit m2m, self-relations, composite `@@id`/`@@unique`, `@@schema` |
|
|
407
|
+
| Django | models as of Django **4.2 / 5.x** — FK / OneToOne / M2M (incl. `through=`), abstract bases, `db_table`/`db_column`, `GenericForeignKey` (kept as a polymorphic marker); a swappable `AUTH_USER_MODEL` FK keeps its column and skips the edge |
|
|
408
|
+
| Python | 3.9+ (`requires-python`); CI runs the latest CPython 3.x |
|
|
409
|
+
|
|
253
410
|
## What you get
|
|
254
411
|
|
|
255
412
|
Feature highlights — each link goes to the relevant [manual](https://orapli.github.io/erdscope/manual.html) chapter:
|
|
@@ -258,7 +415,8 @@ Feature highlights — each link goes to the relevant [manual](https://orapli.gi
|
|
|
258
415
|
column comments, indexes, and real FK constraints, read from the database catalog
|
|
259
416
|
(`information_schema` on MySQL, `pg_catalog` on PostgreSQL)
|
|
260
417
|
- **Code semantics on top** — `--models` merges Rails / Prisma / Django associations;
|
|
261
|
-
declared, DB-FK,
|
|
418
|
+
declared, DB-FK, schema FK (a statically-parsed `rails.schema` foreign key), and
|
|
419
|
+
inferred edges stay [visually distinct](https://orapli.github.io/erdscope/manual.html#viewer-edges)
|
|
262
420
|
- **[Interactive exploration](https://orapli.github.io/erdscope/manual.html#viewer-guide)** — focus with depth and dependency
|
|
263
421
|
direction, two-level hiding, table *and column* search (with regex/case toggles), a
|
|
264
422
|
non-filtering Highlight search that survives into exports, named views, share links
|
|
@@ -273,6 +431,12 @@ Feature highlights — each link goes to the relevant [manual](https://orapli.gi
|
|
|
273
431
|
the live view and exports
|
|
274
432
|
- **Extras** — a built-in `?` shortcuts/help popup, dark mode, print stylesheet,
|
|
275
433
|
resizable/collapsible panes
|
|
434
|
+
- **[Notes](#notes-attach-design-decisions-to-the-diagram)** — attach design decisions,
|
|
435
|
+
operational rules, and ADR links to a table, a relation, or the whole diagram, validated
|
|
436
|
+
against the real schema and searchable alongside tables/columns
|
|
437
|
+
- **[Groups](#groups-draw-a-frame-around-related-tables)** — draw a rounded, titled frame
|
|
438
|
+
around a set of related tables to call out a domain, purely visual, draggable by its
|
|
439
|
+
title, with its own toolbar toggle and export support
|
|
276
440
|
|
|
277
441
|
## Tests
|
|
278
442
|
|
|
@@ -281,7 +445,11 @@ python3 -m unittest discover -s tests -v
|
|
|
281
445
|
```
|
|
282
446
|
|
|
283
447
|
The IR builders and the Excel writer are covered by pure unit tests; the overlay
|
|
284
|
-
parsers have
|
|
448
|
+
parsers have fixtures under `tests/fixture_*` — basic plus advanced constructs per
|
|
449
|
+
framework — and every input provider is additionally held to one shared contract
|
|
450
|
+
(`tests/test_provider_contract.py`): typed dispatch, standalone HTML/Excel generation,
|
|
451
|
+
merging over a DB layer, 1:N / 1:1 / M:N / self-reference, association provenance, and
|
|
452
|
+
empty-input diagnostics, all over the same small domain. No database is required to
|
|
285
453
|
run the tests.
|
|
286
454
|
|
|
287
455
|
`tests/test_e2e.py` drives the generated HTML's client-side JS (grid layout,
|
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
[](https://github.com/orapli/erdscope/actions/workflows/ci.yml)
|
|
4
4
|
[](https://pypi.org/project/erdscope/)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
**Interactive ER diagrams and documented schema definitions.** Generate a
|
|
7
|
+
**self-contained, interactive ER diagram** — and an **Excel table-definition
|
|
7
8
|
workbook** — from a live MySQL, PostgreSQL, or SQLite database, with a single-file,
|
|
8
|
-
zero-dependency Python CLI.
|
|
9
|
+
zero-dependency Python CLI. Turn an ER diagram into a documented schema: attach design
|
|
10
|
+
decisions, operational rules, and ADR links to tables and relationships with config
|
|
11
|
+
[`notes:`](#notes-attach-design-decisions-to-the-diagram) — plain text and http(s) links
|
|
12
|
+
only, validated against the real schema so a note can never point at something that
|
|
13
|
+
doesn't exist — and draw a rounded, titled frame around a set of related tables with config
|
|
14
|
+
[`groups:`](#groups-draw-a-frame-around-related-tables).
|
|
9
15
|
|
|
10
16
|
```bash
|
|
11
17
|
pip install erdscope
|
|
@@ -20,9 +26,10 @@ erdscope sqlite:///path/to/app.db -o erd.html
|
|
|
20
26
|
|
|
21
27
|
- **Database** (MySQL / PostgreSQL / SQLite) — the source of truth for tables, columns,
|
|
22
28
|
comments, indexes, and real foreign keys.
|
|
23
|
-
- **Application code** (`--models`: Rails / Prisma / Django
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
- **Application code** (`--models`: Rails / Prisma / Django, or a Rails `db/schema.rb` —
|
|
30
|
+
see [Typed input sources](#typed-input-sources-sources) below) — adds association
|
|
31
|
+
semantics the database cannot express (`has_many :through`, polymorphic, ...), and can
|
|
32
|
+
stand on its own when there is no DB to point at.
|
|
26
33
|
- **Config file** (`tables:`) — declare or patch a schema by hand: add tables, columns,
|
|
27
34
|
indexes, and associations, or override and delete what the DB or code got wrong.
|
|
28
35
|
|
|
@@ -191,6 +198,138 @@ something that actually exists once all sources are merged) — typos never pass
|
|
|
191
198
|
The full `tables:` schema, the `drop`/`replace` operations, and the precedence rules are
|
|
192
199
|
documented in the [manual](https://orapli.github.io/erdscope/manual.html#config-file).
|
|
193
200
|
|
|
201
|
+
### Typed input sources (`sources:`)
|
|
202
|
+
|
|
203
|
+
`--models`/config `models` auto-detect what kind of project a path is. The config-only
|
|
204
|
+
**`sources:`** list is the typed alternative: each entry names its own `type`, so nothing
|
|
205
|
+
needs detecting and several inputs can be declared explicitly and unambiguously —
|
|
206
|
+
including **`rails.schema`**, which statically parses a Rails `db/schema.rb` file
|
|
207
|
+
(columns, indexes, real foreign keys) with **no live database and no Ruby execution**:
|
|
208
|
+
|
|
209
|
+
```yaml
|
|
210
|
+
version: 1
|
|
211
|
+
sources:
|
|
212
|
+
- id: schema
|
|
213
|
+
type: rails.schema
|
|
214
|
+
path: db/schema.rb
|
|
215
|
+
- id: app
|
|
216
|
+
type: rails.models # every registered overlay gets its own <name>.models type
|
|
217
|
+
path: app/models
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
(The optional top-level `version: 1` is a config-format marker — currently the only
|
|
221
|
+
supported value, with no other effect; it's for future config-format changes to key off.)
|
|
222
|
+
|
|
223
|
+
A typed source that parses **nothing** — say, a Prisma project accidentally declared as
|
|
224
|
+
`rails.models` — is a hard error naming the source id and the layout the type expected,
|
|
225
|
+
never a silently empty diagram. If an empty result is genuinely intended (a scaffolded
|
|
226
|
+
but still-empty `app/models`, for example), opt in per source with `allow_empty: true`;
|
|
227
|
+
a `rails.project` entry passes the flag down to both of its expanded halves.
|
|
228
|
+
|
|
229
|
+
A **`rails.project`** entry is a macro for a whole Rails app root: it expands to both the
|
|
230
|
+
`rails.schema` (`<root>/db/schema.rb`) and `rails.models` (`<root>/app/models`) halves,
|
|
231
|
+
whichever exist —
|
|
232
|
+
|
|
233
|
+
```yaml
|
|
234
|
+
sources:
|
|
235
|
+
- id: app
|
|
236
|
+
type: rails.project
|
|
237
|
+
path: ../myapp
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
is equivalent to declaring both `rails.schema` and `rails.models` entries above. A
|
|
241
|
+
`schema.rb`-derived foreign key merges as a **schema FK** (a teal badge in the viewer,
|
|
242
|
+
distinct from a live `DB FK`), and the merge authority order extends to
|
|
243
|
+
**Config > live DB > rails.schema > models** (physical facts) — a `schema.rb` dump is
|
|
244
|
+
closer to the real database than code is, but a live DB read still wins when both are
|
|
245
|
+
present. See the [Input sources chapter of the manual](https://orapli.github.io/erdscope/manual.html#input-sources)
|
|
246
|
+
for the full `sources[]` reference.
|
|
247
|
+
|
|
248
|
+
### Notes: attach design decisions to the diagram
|
|
249
|
+
|
|
250
|
+
Config **`notes:`** attaches short, plain-text write-ups — design decisions, operational
|
|
251
|
+
rules, ADR links — to a table, a specific relation, or the whole diagram. Notes never
|
|
252
|
+
touch the schema itself (no effect on columns, associations, or merge precedence); they're
|
|
253
|
+
a read-only sidecar validated against the final merged schema and rendered next to it:
|
|
254
|
+
|
|
255
|
+
```yaml
|
|
256
|
+
notes:
|
|
257
|
+
- id: user-retention
|
|
258
|
+
target: { type: table, table: users }
|
|
259
|
+
title: Retention policy
|
|
260
|
+
text: Suspended accounts are kept for 1 year, then anonymized.
|
|
261
|
+
links:
|
|
262
|
+
- { label: ADR-004, url: https://example.com/adr/004 }
|
|
263
|
+
|
|
264
|
+
- id: order-ownership
|
|
265
|
+
target: { type: relation, source_table: orders, target_table: users, foreign_key: user_id }
|
|
266
|
+
text: Orders are kept after a user is anonymized (financial record-keeping).
|
|
267
|
+
|
|
268
|
+
- id: diagram-conventions
|
|
269
|
+
target: { type: global }
|
|
270
|
+
title: How to read this diagram
|
|
271
|
+
text: The dotted amber edge is an inferred relation, not a real FK.
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
- Every note needs a config-unique `id` and non-empty `text`; `title` and `links` are
|
|
275
|
+
optional. Link `url`s must be `http://` or `https://` — anything else (`javascript:`,
|
|
276
|
+
`data:`, a bare string) is rejected at load time.
|
|
277
|
+
- `target.type` is `table`, `relation`, or `global`. A `relation` note is identified by
|
|
278
|
+
`source_table` (the side that **holds** the association — the `belongs_to`/FK-holding
|
|
279
|
+
side for a `belongs_to`, the owning side for a `has_many`) and `target_table`;
|
|
280
|
+
`foreign_key`/`name`/`assoc_type`/`through`/`polymorphic` are optional narrowing keys for
|
|
281
|
+
when a table has more than one relation to the same target (`assoc_type` is the
|
|
282
|
+
association kind — `has_many`/`belongs_to`/`has_one`/`has_and_belongs_to_many` — which
|
|
283
|
+
tells apart, say, a `has_many` and a `has_one` that share a name and target). A note that
|
|
284
|
+
matches no relation, or more than one, is a hard error naming the note's `id` — never a
|
|
285
|
+
silent guess.
|
|
286
|
+
- Validation runs **twice**, like `tables:` above: syntax at load time, then semantically
|
|
287
|
+
against the schema that results **after** DB/`--models`/config `tables:` are all merged
|
|
288
|
+
— so a note can reference a table or relation that config itself adds, and a note on
|
|
289
|
+
something config *removes* is correctly an error.
|
|
290
|
+
- Rendered as plain, HTML-escaped text only — no Markdown, no raw HTML, no scripts — in
|
|
291
|
+
the table's detail panel, next to the matching relation, or in the diagram's legend for
|
|
292
|
+
a `global` note. Notes are also searchable (title, text, link labels). A note whose
|
|
293
|
+
table is hidden from the current view simply doesn't appear in the (now absent) detail
|
|
294
|
+
panel; a `global` note's legend entry is unaffected.
|
|
295
|
+
- `write_excel` accepts the same notes but doesn't use them yet in this release — an Excel
|
|
296
|
+
Notes sheet is a possible future addition, not implemented today.
|
|
297
|
+
|
|
298
|
+
See the [Notes chapter of the manual](https://orapli.github.io/erdscope/manual.html#notes)
|
|
299
|
+
for the full reference.
|
|
300
|
+
|
|
301
|
+
### Groups: draw a frame around related tables
|
|
302
|
+
|
|
303
|
+
Config **`groups:`** draws a rounded frame with a title around a set of related tables in
|
|
304
|
+
the diagram — a lightweight way to call out a domain ("Billing", "Orders") without changing
|
|
305
|
+
the schema or the layout. Like `notes:`, it's a read-only sidecar validated against the
|
|
306
|
+
final merged schema:
|
|
307
|
+
|
|
308
|
+
```yaml
|
|
309
|
+
groups:
|
|
310
|
+
- id: billing
|
|
311
|
+
title: Billing
|
|
312
|
+
tables: [invoices, payments, coupons]
|
|
313
|
+
color: "#0d9488"
|
|
314
|
+
|
|
315
|
+
- id: catalog
|
|
316
|
+
tables: [products, categories, product_categories]
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
- Every group needs a config-unique `id` and non-empty `tables`; `title` (defaults to `id`)
|
|
320
|
+
and `color` (a hex string, e.g. `#0d9488`) are optional.
|
|
321
|
+
- A table may belong to **at most one** group — claiming the same table from two groups is
|
|
322
|
+
a hard error naming both group `id`s and the table, not a silently-picked winner. There's
|
|
323
|
+
no support for overlapping/nested groups in this release.
|
|
324
|
+
- Validation runs twice, like `notes:`/`tables:` above: syntax at load time, then
|
|
325
|
+
semantically against the final merged schema, so a group can reference a table config
|
|
326
|
+
itself adds, and a group naming something config *removes* is correctly an error.
|
|
327
|
+
- Purely visual: groups never affect layout, merge precedence, or associations — the frame
|
|
328
|
+
is drawn around wherever its member tables already ended up. In the viewer, drag a
|
|
329
|
+
group's title to move every member together; a "Groups" toolbar toggle shows/hides the
|
|
330
|
+
frames, and both PNG and SVG exports include them. `--only`/`--exclude` narrow a group's
|
|
331
|
+
membership down to the surviving tables, dropping the group entirely if none remain.
|
|
332
|
+
|
|
194
333
|
## Dependencies
|
|
195
334
|
|
|
196
335
|
`erd.py` runs with **zero required dependencies** — everything below is optional, and
|
|
@@ -216,6 +355,24 @@ Test-only, and only if you run that particular suite:
|
|
|
216
355
|
| [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 |
|
|
217
356
|
| [Playwright](https://playwright.dev/python/) | The browser E2E suite (`tests/test_e2e.py`) — see [Tests](#tests) below |
|
|
218
357
|
|
|
358
|
+
## Verified versions
|
|
359
|
+
|
|
360
|
+
The input formats erdscope parses change rarely, so exact versions matter less than
|
|
361
|
+
they might seem — newer releases are expected to keep working, though constructs
|
|
362
|
+
beyond the tested ones may be ignored rather than parsed. For the record, this is what
|
|
363
|
+
each input is actually verified against:
|
|
364
|
+
|
|
365
|
+
| Input | Verified against |
|
|
366
|
+
|---|---|
|
|
367
|
+
| MySQL | **8.4** — real-server integration tests in CI (`information_schema`); developed against 8.x |
|
|
368
|
+
| PostgreSQL | **16** — real-server integration tests in CI (`pg_catalog`/`information_schema`) |
|
|
369
|
+
| SQLite | the `sqlite3` module bundled with CPython (any supported 3.x) |
|
|
370
|
+
| Rails `schema.rb` | the format Rails **7.x / 8.x** writes (`ActiveRecord::Schema[7.x]`, and the classic un-versioned header) |
|
|
371
|
+
| Rails models | the association DSL as of Rails **7.x** — `has_many`/`has_one`/`belongs_to`/`has_and_belongs_to_many`, `through:`, `polymorphic:`, STI, concerns, custom base classes; also exercised against Mastodon's real codebase. Dynamically computed definitions (and `structure.sql`) are out of scope |
|
|
372
|
+
| Prisma | the schema language as of Prisma **5 / 6** — `@map`/`@@map`, enums, named relations, implicit and explicit m2m, self-relations, composite `@@id`/`@@unique`, `@@schema` |
|
|
373
|
+
| Django | models as of Django **4.2 / 5.x** — FK / OneToOne / M2M (incl. `through=`), abstract bases, `db_table`/`db_column`, `GenericForeignKey` (kept as a polymorphic marker); a swappable `AUTH_USER_MODEL` FK keeps its column and skips the edge |
|
|
374
|
+
| Python | 3.9+ (`requires-python`); CI runs the latest CPython 3.x |
|
|
375
|
+
|
|
219
376
|
## What you get
|
|
220
377
|
|
|
221
378
|
Feature highlights — each link goes to the relevant [manual](https://orapli.github.io/erdscope/manual.html) chapter:
|
|
@@ -224,7 +381,8 @@ Feature highlights — each link goes to the relevant [manual](https://orapli.gi
|
|
|
224
381
|
column comments, indexes, and real FK constraints, read from the database catalog
|
|
225
382
|
(`information_schema` on MySQL, `pg_catalog` on PostgreSQL)
|
|
226
383
|
- **Code semantics on top** — `--models` merges Rails / Prisma / Django associations;
|
|
227
|
-
declared, DB-FK,
|
|
384
|
+
declared, DB-FK, schema FK (a statically-parsed `rails.schema` foreign key), and
|
|
385
|
+
inferred edges stay [visually distinct](https://orapli.github.io/erdscope/manual.html#viewer-edges)
|
|
228
386
|
- **[Interactive exploration](https://orapli.github.io/erdscope/manual.html#viewer-guide)** — focus with depth and dependency
|
|
229
387
|
direction, two-level hiding, table *and column* search (with regex/case toggles), a
|
|
230
388
|
non-filtering Highlight search that survives into exports, named views, share links
|
|
@@ -239,6 +397,12 @@ Feature highlights — each link goes to the relevant [manual](https://orapli.gi
|
|
|
239
397
|
the live view and exports
|
|
240
398
|
- **Extras** — a built-in `?` shortcuts/help popup, dark mode, print stylesheet,
|
|
241
399
|
resizable/collapsible panes
|
|
400
|
+
- **[Notes](#notes-attach-design-decisions-to-the-diagram)** — attach design decisions,
|
|
401
|
+
operational rules, and ADR links to a table, a relation, or the whole diagram, validated
|
|
402
|
+
against the real schema and searchable alongside tables/columns
|
|
403
|
+
- **[Groups](#groups-draw-a-frame-around-related-tables)** — draw a rounded, titled frame
|
|
404
|
+
around a set of related tables to call out a domain, purely visual, draggable by its
|
|
405
|
+
title, with its own toolbar toggle and export support
|
|
242
406
|
|
|
243
407
|
## Tests
|
|
244
408
|
|
|
@@ -247,7 +411,11 @@ python3 -m unittest discover -s tests -v
|
|
|
247
411
|
```
|
|
248
412
|
|
|
249
413
|
The IR builders and the Excel writer are covered by pure unit tests; the overlay
|
|
250
|
-
parsers have
|
|
414
|
+
parsers have fixtures under `tests/fixture_*` — basic plus advanced constructs per
|
|
415
|
+
framework — and every input provider is additionally held to one shared contract
|
|
416
|
+
(`tests/test_provider_contract.py`): typed dispatch, standalone HTML/Excel generation,
|
|
417
|
+
merging over a DB layer, 1:N / 1:1 / M:N / self-reference, association provenance, and
|
|
418
|
+
empty-input diagnostics, all over the same small domain. No database is required to
|
|
251
419
|
run the tests.
|
|
252
420
|
|
|
253
421
|
`tests/test_e2e.py` drives the generated HTML's client-side JS (grid layout,
|