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

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:

  • schema writes the graphgen-compatible schema.json — the portable artifact graphgen and databrowse read directly. Relations come out as FK:<Model> and M2M:<Model>.
  • datagen writes a BaseDataGenerator subclass for 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.

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.