Metadata-Version: 2.4
Name: spltz-viur-models
Version: 0.1.0
Summary: SQLmodel definitions for ViUR applications.
Author: Andreas H. Kelch
License: MIT License
        
        Copyright © 2026 Andreas H. Kelch
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/sprengplatz/viur-models
Project-URL: Repository, https://github.com/sprengplatz/viur-models.git
Project-URL: Bug Tracker, https://github.com/sprengplatz/viur-models/issues
Project-URL: Changelog, https://github.com/sprengplatz/viur-models/blob/main/CHANGELOG.md
Keywords: viur,models,skeletons,bones
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: viur-core<4,>=3.8
Requires-Dist: spltz-viur-actions<1.0,>=0.4
Requires-Dist: sqlmodel>=0.0.39
Requires-Dist: pydantic-extra-types>=2.10
Requires-Dist: pycountry>=24.6
Requires-Dist: email-validator>=2
Provides-Extra: migrations
Requires-Dist: alembic>=1.13; extra == "migrations"
Provides-Extra: bigquery
Requires-Dist: sqlalchemy-bigquery>=1.11; extra == "bigquery"
Provides-Extra: test
Requires-Dist: pytest~=8.0; extra == "test"
Requires-Dist: pytest-cov~=5.0; extra == "test"
Requires-Dist: coverage[toml]~=7.0; extra == "test"
Requires-Dist: spltz-viur-light-mock<1.0,>=0.3; extra == "test"
Requires-Dist: alembic>=1.13; extra == "test"
Provides-Extra: docs
Requires-Dist: mkdocs-material~=9.5; extra == "docs"
Requires-Dist: mkdocstrings[python]~=0.26; extra == "docs"
Requires-Dist: mkdocs-static-i18n~=1.2; extra == "docs"
Provides-Extra: dev
Requires-Dist: spltz-viur-models[bigquery,docs,migrations,test]; extra == "dev"
Requires-Dist: build~=1.2; extra == "dev"
Dynamic: license-file

# viur-models

SQLModel definitions for ViUR applications.

[![Tests](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml/badge.svg)](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

## 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/](analysis/README.md) for the design and
[CHANGELOG.md](CHANGELOG.md) for the running summary.

## Requirements

- Python ≥ 3.12
- viur-core ≥ 3.8, < 4

## Install

```bash
pip install spltz-viur-models
```

## Quick taste

```python
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:

```python
Slug = t.Annotated[str, BoneType("str.slug")]
```

The bone-by-bone mapping (skeleton declaration vs. field equivalent) is
documented in [docs/bones.md](docs/bones.md).

Serve a model like a skeleton module — same endpoints, same envelope-v2
wire format:

```python
# 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](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:

```bash
pip install "spltz-viur-models[migrations]"
```

The scaffold is generated on the dev server — one argument, no `alembic init`:

```python
# 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
```

```bash
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:

```python
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](docs/migrations.md), design rationale in
[analysis/03](analysis/03-migrations.md).

## Development

Two test layers, run separately:

**Unit (fast, mocked) — `tests/`, 100 % coverage gate:**

```bash
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:**

```bash
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](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](https://sprengplatz.github.io/viur-models/)
— available in English and German (`/de/`).

## License

MIT — see [LICENSE](LICENSE).
