Skip to content

Feature: erwin import - #43

Merged
dbose merged 9 commits into
mainfrom
feat-erwin-import
Sep 26, 2026
Merged

dbose merged 9 commits into
mainfrom
feat-erwin-import

Conversation

@dbose

@dbose dbose commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Title: Real erwin XML import — streaming parser, rich mapping, three entry points, surfaced in the canvas

Body:

What this changes

Turns the parked erwin import into a real feature. The existing importer was written against an invented flat XML shape and silently produced empty models on real exports — this rewrites it against the actual erwin Data Modeler schema (from the extracted erwin/schemas/*.xsd) and surfaces the imported data end to end.

  • Real streaming parser. mdl_reverse/erwin.py is rewritten: namespaced, GUID-referenced, properties read as element text inside <XxxProps>. It uses ET.iterparse + elem.clear() so a 7-10MB export never materializes as a DOM (verified on a 2000-entity, >1MB fixture), then resolves cross-references in a second pass.
  • Rich, faithful mapping. Entities (logical + physical names), primary/alternate/unique key groups, relationships (with cardinality, identifying, and erwin verb phrases), subtype/supertype → categories (with discriminator), subject-area membership, and domains. FK columns inherit the parent PK's domain (erwin type migration). Anything Modelith doesn't model (views, ER-diagram layout, physical transforms) is skipped with a note, never a failure.
  • No flattening. Erwin produces a full IR Model, and a new model_to_commands() bridges it to the same {op, payload} list the canvas/CLI apply path uses — so subject areas, categories, and domains survive instead of being squeezed through the thin table-only importer.
  • CLI. mdl import erwin <file> -o <dir> writes a fresh model (with the reverse no-clobber divert); --apply merges into an existing model via the mutation engine.
  • Three VS Code entry points, no dedicated frame (import is a one-shot action; a button-only frame would be a hollow view): the command palette (Modelith: Import Erwin XML), a Reverse Review title-bar button, and an .xml Explorer right-click.
  • Canvas + server. An "erwin XML" option in the Import wizard and an erwin branch in /api/import.
  • Surfaced in the UI. The projection now emits verb phrases + physical names; the Inspector shows and edits the entity/attribute physical names and the relationship verb phrases ("is party to" / "is with"); the ER edge label uses the verb phrase when selected. Editing rides the existing command engine (update_relationship, update_attribute, rename_entity gained the new fields).

Small additive IR fields make this faithful: Relationship.verb_phrase/inverse_verb_phrase, LogicalEntity.physical_name, Attribute.physical_name — all optional, so existing models are unchanged.

How it was tested

  • uv run pytest — 900 passed, 1 skipped. New tests: the reader on a real-shaped fixture (entities/keys, relationship + verb phrases, subtype → category, subject-area membership, domains, skip-with-warnings, the model validates, streaming large export); model_to_commands round-trips; the CLI (write / --apply / divert); the /api/import erwin branch; and the projection-emits + commands-edit the new fields.
  • uv run ruff check packages/ — clean.
  • Extension: npm run typecheck && npm run regression (Node 20) — all 6 gates, 42 commands registered.
  • Canvas: npm run build (Node 20) — typechecks + builds into the server static.
  • Manual: Yet to complete !!!

Versions

CLI 0.6.6, extension 0.3.14.

Small additive IR fields the erwin importer needs so a real erwin model imports
faithfully and a re-export round-trips:
- LogicalEntity.physical_name and Attribute.physical_name (erwin Physical_Name, distinct
  from the logical name)
- Relationship.verb_phrase / inverse_verb_phrase (erwin Parent_To_Child /
  Child_To_Parent verb phrases, e.g. "places" / "is placed by")

All optional and defaulted to None, so a model that doesn't use them is unchanged and
dumps clean (exclude_none drops them). Round-trip test confirms they survive write+load;
core suite green.
The old import_erwin was written against an invented flat shape (<Entity name=
subject_area=>, parent=/child= by name) that does not match real erwin exports at all,
so it silently produced an empty model. Rewritten against the actual erwin Data Modeler
format (from erwin/schemas/*.xsd):

- Namespaced {http://www.erwin.com/dm}erwin -> {.../dm/data}Model, GUID-referenced
  objects, properties as element TEXT inside a sibling <XxxProps> (not XML attributes).
- Streaming parse (ElementTree.iterparse + elem.clear() per top-level object) so a
  7-10MB export never materialises as a DOM. Verified on a 2000-entity >1MB fixture.
- Two-pass: index every object by id, then resolve cross-refs (Parent/Child_Entity_Ref,
  Key_Group_Member/Attribute_Ref, Parent_Domain_Ref, subject-area attached-object arrays).
- Maps: Entity -> Conceptual+Logical (logical name lowercased, Physical_Name kept),
  per-entity Key_Group -> KeyGroup (pk/alternate/unique), Relationship -> Relationship
  (child=from/many, parent=to/one, verb phrases, cardinality, identifying), subtype
  relationship -> Category (with discriminator), Subject_Area -> SubjectArea membership,
  Domain -> Domain. FK columns inherit the parent PK's domain (erwin type migration).
- Defensive: unknown groups (View, ER_Diagram, transforms) and dangling refs skip with a
  warning, never fatal. Returns ErwinImport(model, warnings).

9 tests on a real-shaped fixture: entities/attrs/keys, relationship + verb phrases,
subtype->Category, subject-area membership, domains, skip-with-warnings, the imported
model validates clean, and the streaming large-export path. CLI wiring follows in the
next commit.
…ply bridge)

The canvas /api/import and the CLI apply path consume to_commands's {op,payload} list,
but to_commands only accepts the thin table-oriented ImportedModel. Erwin produces a full
IR Model (subject areas, categories, domains, conceptual entities, keys) that would be
flattened away going through ImportedModel.

model_to_commands(model) walks a full Model and emits the same op vocabulary
(create_subject_area, create_domain, create_entity with subject-area home tag,
set_subject_area_members for the inclusion list, add_attribute, create_key_group,
create_relationship, create_category with discriminator), minting fresh ULIDs in a
dependency-safe order. This lets the erwin importer ride the exact preview/apply path the
simple importers use, without losing its richness.

3 round-trip tests (structure reconstructs, the result validates clean, a category with a
discriminator round-trips). apply_command reconstructs the model faithfully.
…o an existing one

Rewrites the erwin import command for the new streaming reader + rich Model:
- passes the file PATH to import_erwin so the 7-10MB streaming path is used
- default: write a fresh model at --out (an erwin export is a whole model, like reverse),
  with the same no-clobber divert to a -reversed-v<N> sibling unless --force
- --apply: merge the erwin objects INTO an existing model at --out via model_to_commands
  -> apply_command (the validated mutation engine), for adding erwin to a model you have
- surfaces the reader's warnings as yellow note: lines

3 CLI tests: write-a-model (validates, notes shown), --apply into an existing model
(merges, validates), and the divert-on-existing behaviour. 471 tests pass across
reverse/cli/emit-erd/core.
- Server /api/import gains an `erwin` branch: import_erwin(content) → model_to_commands,
  returning the same {ok, changes, tables, warnings} shape as the other formats, so the
  canvas previews/applies it through the existing path. Uses the rich Model bridge, not
  the thin table parsers, so subject areas / categories / domains survive.
- Canvas Import menu: adds an "erwin XML" format option and `.xml` to the file accept.
  Rebuilt into the server static.

2 server tests (erwin returns the right ops + warnings; empty content is 422). Canvas
typechecks + builds.
…ight-click

Three entry points for the erwin importer, no dedicated panel frame (import is a one-shot
action, not monitored state — a button-only frame would be the blank-void anti-pattern):
- `modelith.importErwin` command -> Cmd+Shift+P "Modelith: Import Erwin XML" (file picker,
  .xml filter). Resolves a target dir via the reverse no-clobber flow (update in place vs
  new folder), runs `mdl import erwin <file> -o <dir>` under progress (5-min timeout for
  10MB files), then opens the canvas on the result and refreshes the panels. Surfaces the
  reader's notes.
- Reverse Review title-bar button (beside Reverse / Reverse Live / Compare) — erwin import
  is a "bring an external model in" action, its natural home.
- Explorer right-click on any .xml -> "Import Erwin XML", passing the clicked URI so it
  skips the picker.

All 6 regression gates pass (42 commands registered); typecheck clean.
Documents `mdl import erwin` (streaming, rich mapping — keys, relationships + verb
phrases, subtype categories, subject areas, domains, physical names; graceful skip of
views/layout/transforms) and the three VS Code entry points.
…and on edges

The importer populated verb_phrase/inverse_verb_phrase and physical_name, but they were
invisible in the UI — imported data the user couldn't see or edit. Now fully surfaced:

- Projection emits verb_phrase/inverse_verb_phrase per relationship and physical_name per
  entity and attribute.
- Inspector: a "physical" name field under the entity title (editable), a physical column
  name beside an attribute when it differs, and the relationship rows show the two verb
  phrases ("is party to" / "is with") inline and editable.
- ER edge label shows the relationship's verb phrase when selected (the erwin reading
  "<parent> <verb> <child>"), falling back to the name.
- Editing rides the existing command engine: update_relationship gains verb_phrase/
  inverse_verb_phrase, update_attribute + rename_entity gain physical_name (empty clears).

Verified in the browser against an erwin-imported model: the Inspector shows PHYSICAL
COUNTERPARTY and the "is party to / is with" verb phrases; edits persist. 4 tests
(projection emits, commands edit all four fields). Canvas rebuilt; extension 0.3.14.
@dbose dbose changed the title Feat erwin import Feature: erwin import Sep 26, 2026
@dbose
dbose merged commit c815393 into main Sep 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant