4.5 KiB
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 |
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 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:
schemawrites the graphgen-compatibleschema.json— the portable artifact graphgen and databrowse read directly. Relations come out asFK:<Model>andM2M:<Model>.datagenwrites aBaseDataGeneratorsubclass for datagen, including itsschema()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.
Tests
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.