112 lines
4.5 KiB
Markdown
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.
|