# Modelgen Multi-source, multi-target model generator. Reads a schema from wherever it already lives, and writes it out for every stack that needs it. **Status:** live --- ## What It Does Everything passes through one intermediate representation — `ModelDefinition`, `FieldDefinition`, `EnumDefinition`. **Loaders** fill it, **generators** emit from it, and the two sides do not know about each other. Adding an input means one extractor and every output comes with it; adding an output means one generator and every input already feeds it. ``` dataclasses ─┐ ┌─ pydantic Django │ ├─ django SQLAlchemy ├──▶ ModelDefinition ──▶├─ sqlmodel a live DB │ FieldDefinition ├─ typescript OpenAPI │ EnumDefinition ├─ protobuf CSV/ODS ─┘ ├─ prisma ├─ strawberry ├─ schema (graphgen) └─ datagen ``` Core is **pure standard library**. It is published as `soleprint-modelgen` and installs with no dependencies; live-database extraction is an extra (`pip install "soleprint-modelgen[db]"`), and YAML specs need PyYAML. ## Sources | Command | Reads | | --- | --- | | `from-schema` | Python dataclasses in a `schema/` folder | | `from-config` | a room's `config.json` | | `extract` | a Django or SQLAlchemy codebase (`--framework auto` detects) | | `from-db` | a live database, any SQLAlchemy dialect | | `from-openapi` | an OpenAPI 3.x / Swagger 2.0 document | | `from-tabular` | a directory of `.csv` / `.tsv` / `.ods` spreadsheets | ```bash python -m station.tools.modelgen from-openapi -s api.yaml -o out/ -t pydantic,typescript,schema python -m station.tools.modelgen from-tabular -s ./sheets -o out/ -t pydantic,datagen python -m station.tools.modelgen extract -s /path/to/django -o out/ -t prisma python -m station.tools.modelgen from-db -u postgresql://… -o out/ -t typescript python -m station.tools.modelgen list-formats ``` ### From a spec `components.schemas` (or Swagger's `definitions`) become models. `$ref` chains and `allOf` are resolved, enums are materialised as real `Enum` classes so every target names them properly, and a referenced object becomes a relation rather than a nested type — the same call the database extractor makes, and what keeps the generated code valid for every target. The parse also yields the *operations*, which is what [shuntgen](#station-shuntgen) turns into routes. ### From spreadsheets One model per CSV file, one per sheet in an ODS workbook. Column types are inferred from the values actually present, and a blank cell makes the column optional. Keys and relations are inferred by name and then confirmed against the data: an `id` column that is not unique is not treated as a key, and `customer_id` is only a foreign key if a `customers` sheet came with it. The rows are kept, not just the shape — which is what lets the `datagen` target sample real values instead of inventing them. ODS is read with `zipfile` and `ElementTree`. No odfpy, no pandas: the dependency-free promise is what makes this package publishable on its own. ## Targets `pydantic`, `django`, `sqlmodel`, `typescript` (`ts`), `protobuf` (`proto`), `prisma`, `strawberry`, `schema` (`jsonschema`), `datagen`. Two are worth calling out: - **`schema`** writes the graphgen-compatible `schema.json` — the portable artifact [graphgen](#station-graphgen) and databrowse read directly. Relations come out as `FK:` and `M2M:`. - **`datagen`** writes a `BaseDataGenerator` subclass for [datagen](#station-datagen), including its `schema()` override. Given spreadsheet rows it samples them; otherwise it synthesises from the types. Multiple targets in one run get one file each, named `models_`. ## In a build `build.py` calls modelgen during every room build, writing `gen//models/pydantic/__init__.py` from the room's `config.json`. See [Export / Compile](#export). ## Tests ```bash cd soleprint/station/tools python -m unittest modelgen.tests.test_extractors ``` stdlib `unittest`, no pytest, and every input is built in a temp directory — the tests have to pass with nothing installed and nothing else in the tree. Run them from `station/tools/`, not from inside `modelgen/`: the package ships a `types.py`, and putting its own directory on `sys.path` shadows the standard library module of that name.