Files
soleprint/docs/data/en/station-modelgen.md
2026-08-10 05:36:32 -03:00

112 lines
4.5 KiB
Markdown

# 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:<Model>` and `M2M:<Model>`.
- **`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_<target><ext>`.
## In a build
`build.py` calls modelgen during every room build, writing
`gen/<room>/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.