viur-models
SQLModel definitions for ViUR applications.
Status
Alpha. Implemented: Field, the bone-type field types
(Text, Email, Country, + BoneType/register_bone_type for your
own), the skeleton-compatible structure/dump emission, fromClient error
mapping, opaque key encoding — the SQLList module prototype
(list/view/add/edit/delete/structure over envelope v2, hooks from
viur-actions, session-per-action) — and schema migrations
(viur.models.migrations, Alembic) with automatically generated
bone-level data migrations. All of it is verified against the
real viur-core (and the real envelope renderer) by the integration
suite. See analysis/ for the design and
CHANGELOG.md for the running summary.
Requirements
- Python ≥ 3.12
- viur-core ≥ 3.8, < 4
Install
pip install spltz-viur-models
Quick taste
from viur.models import Country, Email, Text, Field, Model
class Feedback(Model, table=True): # a plain SQLModel underneath
name: str = Field(descr="Name", max_length=100)
mail: Email = Field(descr="E-Mail") # bone type "str.email"
message: Text = Field(default="") # bone type "text"
country: Country | None = Field(default=None) # "select.country"
Feedback.viur_structure() # skeleton-compatible structure dict
Feedback(id=42).viur_key # opaque key string, like a datastore key
The bone type is decided by the Python type — str, int, bool,
datetime, enum/Literal map automatically; semantic types come from
the pydantic ecosystem (e.g. Country is pydantic-extra-types'
CountryAlpha2) or are one Annotated alias away:
Slug = t.Annotated[str, BoneType("str.slug")]
The bone-by-bone mapping (skeleton declaration vs. field equivalent) is documented in docs/bones.md.
Serve a model like a skeleton module — same endpoints, same envelope-v2 wire format:
# deploy/modules/feedback.py
from viur.models.sqllist import SQLList
from models.feedback import Feedback
class feedback(SQLList):
model = Feedback
def can(self, instance): # fail-closed by default; open up per
return True # action via canView/canEdit/… overrides
# deploy/main.py — two calls, one either side of core.setup():
import viur.models
viur.models.install(engine="postgres", # NullPool on App Engine
postgres_dsn="postgresql+pg8000://…")
app = core.setup(modules, render)
viur.models.setup() # needs the models imported
install() builds the engine and wires the cross-store refresh hooks;
setup() reports the schema state (and bootstraps it for the in-memory
preset). Details in docs/getting-started.md.
Migrations
create_all() bootstraps a schema but never changes one — it silently
ignores every altered column, removed field and changed type. Install the
extra and let Alembic do it:
pip install "spltz-viur-models[migrations]"
The scaffold is generated on the dev server — one argument, no alembic init:
# deploy/main.py, after core.setup()
viur.models.setup(migrations=PROJECT_ROOT)
It writes only what is missing (hand edits survive), is inert on a deployed
instance, and puts the first revision in place right away: a fresh database
gets upgrade head, one that already has the tables gets stamped with a
revision autogenerated against an empty probe database — so nothing is
dropped and the history still describes the full schema.
The result lives next to the distribution folder, not inside it — Alembic is build-time tooling and has no business being uploaded with the app:
myproject/
alembic.ini # prepend_sys_path = %(here)s/deploy
migrations/
env.py # 4 lines: import_models() + run()
versions/ # the revisions — commit these, they are code
deploy/ # what actually gets deployed
alembic revision --autogenerate -m "add slug to entry"
alembic upgrade head
alembic check # CI gate: do models and schema still agree?
Bone-level changes bring their own data migration. A skeleton project just edits the bone and viur-core coerces on read; in SQL the same change moves data, and Alembic would drop the link table first. Eight transitions are detected and generated — following viur-core's own rules:
def upgrade() -> None:
op.reduce_languages("post", "title", new_type=sa.String(200),
languages=["de", "en"], keep="de")
op.collapse_multiple("post", link_table="post_tag", target_column="tag_id",
link_parent_fk="post_id", link_dest_fk="tag_id",
foreign_table="tag", keep="first") # = loadVal[0]
multiple ↔ single, multilingual ↔ plain, str ↔ Text, numeric precision,
select → bool, new/removed fields and new using payload columns. Only
bool → select needs a line from you — viur-core has no rule there, so
the generator emits a stub that refuses to run until the mapping is filled.
Detection runs on structure snapshots, not on Alembic's DDL diff, which
cannot see str → Text at all.
The module also handles what a stock Alembic scaffold does not: resolving
the database URL without booting viur-core, rendering custom
TypeDecorator columns (RecordJSON) as their DDL type so revisions stay
frozen snapshots, and SQLite's batch mode. Details in
docs/migrations.md, design rationale in
analysis/03.
Development
Two test layers, run separately:
Unit (fast, mocked) — tests/, 100 % coverage gate:
git clone https://github.com/sprengplatz/viur-models
cd viur-models
pip install --no-deps -e .
pip install pytest pytest-cov 'coverage[toml]' 'spltz-viur-light-mock>=0.3,<1.0' 'spltz-viur-actions>=0.4,<1.0' sqlmodel pydantic-extra-types pycountry email-validator 'alembic>=1.13'
pytest # 100% coverage required
viur-light-mock provides the viur.core.* stand-ins so these run without
the App Engine stack.
Integration (real core) — integration/, no coverage gate:
pip install "viur-core>=3.8,<4" rsa pytest 'spltz-viur-actions>=0.4,<1.0' sqlmodel pydantic-extra-types pycountry email-validator
pip install --no-deps -e .
python -m pytest -c integration/pytest.ini integration
Runs against the real framework — the layer that catches mock-vs-core drift. See integration/README.md.
Coverage policy. The 100 % gate applies to the unit layer only. The integration layer runs without a coverage requirement — a coverage target there would pressure mocking the very framework it exists to exercise.
Documentation
sprengplatz.github.io/viur-models
— available in English and German (/de/).
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file spltz_viur_models-0.2.0.tar.gz.
File metadata
- Download URL: spltz_viur_models-0.2.0.tar.gz
- Upload date:
- Size: 121.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a997fee1f2b9a4ccc44839702797cfba696f4f97f29a0976c2f638a5fcfbce72
|
|
| MD5 |
71b876856579adb8e06ea5c63404f0af
|
|
| BLAKE2b-256 |
2f3577b13eb2bed55af42c8efb8295a0e4aebdbcb462c680281d828172262708
|
Provenance
The following attestation bundles were made for spltz_viur_models-0.2.0.tar.gz:
Publisher:
release.yml on sprengplatz/viur-models
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spltz_viur_models-0.2.0.tar.gz -
Subject digest:
a997fee1f2b9a4ccc44839702797cfba696f4f97f29a0976c2f638a5fcfbce72 - Sigstore transparency entry: 2780638852
- Sigstore integration time:
-
Permalink:
sprengplatz/viur-models@20548effd1d9671981a81e4e69fb6d2474ba3bbc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/sprengplatz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@20548effd1d9671981a81e4e69fb6d2474ba3bbc -
Trigger Event:
push
-
Statement type:
File details
Details for the file spltz_viur_models-0.2.0-py3-none-any.whl.
File metadata
- Download URL: spltz_viur_models-0.2.0-py3-none-any.whl
- Upload date:
- Size: 63.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2e50f2cb5eb740eadf399dd506852dbc5ff7e45b5e7a84a0990c923b4fdd343
|
|
| MD5 |
1bd4de27787f40e7e57328b0e5fb50eb
|
|
| BLAKE2b-256 |
2665e5159459dfd3cbb2d3647da72e3bb57b08f33d1b41e97842a1c1f56c041d
|
Provenance
The following attestation bundles were made for spltz_viur_models-0.2.0-py3-none-any.whl:
Publisher:
release.yml on sprengplatz/viur-models
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spltz_viur_models-0.2.0-py3-none-any.whl -
Subject digest:
c2e50f2cb5eb740eadf399dd506852dbc5ff7e45b5e7a84a0990c923b4fdd343 - Sigstore transparency entry: 2780638917
- Sigstore integration time:
-
Permalink:
sprengplatz/viur-models@20548effd1d9671981a81e4e69fb6d2474ba3bbc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/sprengplatz
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@20548effd1d9671981a81e4e69fb6d2474ba3bbc -
Trigger Event:
push
-
Statement type: