spltz-viur-models 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. spltz_viur_models-0.1.0/LICENSE +21 -0
  2. spltz_viur_models-0.1.0/PKG-INFO +262 -0
  3. spltz_viur_models-0.1.0/README.md +195 -0
  4. spltz_viur_models-0.1.0/pyproject.toml +101 -0
  5. spltz_viur_models-0.1.0/setup.cfg +4 -0
  6. spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/PKG-INFO +262 -0
  7. spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/SOURCES.txt +49 -0
  8. spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/dependency_links.txt +1 -0
  9. spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/requires.txt +28 -0
  10. spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/top_level.txt +1 -0
  11. spltz_viur_models-0.1.0/src/viur/models/__init__.py +91 -0
  12. spltz_viur_models-0.1.0/src/viur/models/base.py +572 -0
  13. spltz_viur_models-0.1.0/src/viur/models/bigquery.py +70 -0
  14. spltz_viur_models-0.1.0/src/viur/models/boot.py +199 -0
  15. spltz_viur_models-0.1.0/src/viur/models/client.py +35 -0
  16. spltz_viur_models-0.1.0/src/viur/models/config.py +36 -0
  17. spltz_viur_models-0.1.0/src/viur/models/crossstore.py +440 -0
  18. spltz_viur_models-0.1.0/src/viur/models/db.py +166 -0
  19. spltz_viur_models-0.1.0/src/viur/models/fields.py +95 -0
  20. spltz_viur_models-0.1.0/src/viur/models/links.py +6 -0
  21. spltz_viur_models-0.1.0/src/viur/models/migrate.py +622 -0
  22. spltz_viur_models-0.1.0/src/viur/models/migrations.py +538 -0
  23. spltz_viur_models-0.1.0/src/viur/models/scaffold.py +159 -0
  24. spltz_viur_models-0.1.0/src/viur/models/schema.py +382 -0
  25. spltz_viur_models-0.1.0/src/viur/models/sqllist.py +601 -0
  26. spltz_viur_models-0.1.0/src/viur/models/structure.py +645 -0
  27. spltz_viur_models-0.1.0/src/viur/models/types.py +202 -0
  28. spltz_viur_models-0.1.0/tests/test_base.py +44 -0
  29. spltz_viur_models-0.1.0/tests/test_bigquery.py +208 -0
  30. spltz_viur_models-0.1.0/tests/test_boot.py +382 -0
  31. spltz_viur_models-0.1.0/tests/test_client.py +96 -0
  32. spltz_viur_models-0.1.0/tests/test_computed.py +145 -0
  33. spltz_viur_models-0.1.0/tests/test_config.py +126 -0
  34. spltz_viur_models-0.1.0/tests/test_crossstore.py +813 -0
  35. spltz_viur_models-0.1.0/tests/test_db.py +71 -0
  36. spltz_viur_models-0.1.0/tests/test_fields.py +37 -0
  37. spltz_viur_models-0.1.0/tests/test_generator.py +725 -0
  38. spltz_viur_models-0.1.0/tests/test_golden.py +26 -0
  39. spltz_viur_models-0.1.0/tests/test_migrate.py +767 -0
  40. spltz_viur_models-0.1.0/tests/test_migrations.py +520 -0
  41. spltz_viur_models-0.1.0/tests/test_package.py +37 -0
  42. spltz_viur_models-0.1.0/tests/test_records.py +229 -0
  43. spltz_viur_models-0.1.0/tests/test_relations.py +533 -0
  44. spltz_viur_models-0.1.0/tests/test_scaffold.py +123 -0
  45. spltz_viur_models-0.1.0/tests/test_schema.py +428 -0
  46. spltz_viur_models-0.1.0/tests/test_sqllist.py +902 -0
  47. spltz_viur_models-0.1.0/tests/test_structure.py +188 -0
  48. spltz_viur_models-0.1.0/tests/test_suite_guard.py +21 -0
  49. spltz_viur_models-0.1.0/tests/test_types.py +60 -0
  50. spltz_viur_models-0.1.0/tests/test_using_relations.py +275 -0
  51. spltz_viur_models-0.1.0/tests/test_v2_features.py +233 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2026 Andreas H. Kelch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: spltz-viur-models
3
+ Version: 0.1.0
4
+ Summary: SQLmodel definitions for ViUR applications.
5
+ Author: Andreas H. Kelch
6
+ License: MIT License
7
+
8
+ Copyright © 2026 Andreas H. Kelch
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/sprengplatz/viur-models
29
+ Project-URL: Repository, https://github.com/sprengplatz/viur-models.git
30
+ Project-URL: Bug Tracker, https://github.com/sprengplatz/viur-models/issues
31
+ Project-URL: Changelog, https://github.com/sprengplatz/viur-models/blob/main/CHANGELOG.md
32
+ Keywords: viur,models,skeletons,bones
33
+ Classifier: Development Status :: 3 - Alpha
34
+ Classifier: Intended Audience :: Developers
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Requires-Python: >=3.12
41
+ Description-Content-Type: text/markdown
42
+ License-File: LICENSE
43
+ Requires-Dist: viur-core<4,>=3.8
44
+ Requires-Dist: spltz-viur-actions<1.0,>=0.4
45
+ Requires-Dist: sqlmodel>=0.0.39
46
+ Requires-Dist: pydantic-extra-types>=2.10
47
+ Requires-Dist: pycountry>=24.6
48
+ Requires-Dist: email-validator>=2
49
+ Provides-Extra: migrations
50
+ Requires-Dist: alembic>=1.13; extra == "migrations"
51
+ Provides-Extra: bigquery
52
+ Requires-Dist: sqlalchemy-bigquery>=1.11; extra == "bigquery"
53
+ Provides-Extra: test
54
+ Requires-Dist: pytest~=8.0; extra == "test"
55
+ Requires-Dist: pytest-cov~=5.0; extra == "test"
56
+ Requires-Dist: coverage[toml]~=7.0; extra == "test"
57
+ Requires-Dist: spltz-viur-light-mock<1.0,>=0.3; extra == "test"
58
+ Requires-Dist: alembic>=1.13; extra == "test"
59
+ Provides-Extra: docs
60
+ Requires-Dist: mkdocs-material~=9.5; extra == "docs"
61
+ Requires-Dist: mkdocstrings[python]~=0.26; extra == "docs"
62
+ Requires-Dist: mkdocs-static-i18n~=1.2; extra == "docs"
63
+ Provides-Extra: dev
64
+ Requires-Dist: spltz-viur-models[bigquery,docs,migrations,test]; extra == "dev"
65
+ Requires-Dist: build~=1.2; extra == "dev"
66
+ Dynamic: license-file
67
+
68
+ # viur-models
69
+
70
+ SQLModel definitions for ViUR applications.
71
+
72
+ [![Tests](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml/badge.svg)](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml)
73
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
74
+
75
+ ## Status
76
+
77
+ **Alpha.** Implemented: `Field`, the bone-type field types
78
+ (`Text`, `Email`, `Country`, + `BoneType`/`register_bone_type` for your
79
+ own), the skeleton-compatible structure/dump emission, fromClient error
80
+ mapping, opaque key encoding — the **`SQLList` module prototype**
81
+ (list/view/add/edit/delete/structure over envelope v2, hooks from
82
+ viur-actions, session-per-action) — and **schema migrations**
83
+ (`viur.models.migrations`, Alembic) with automatically generated
84
+ bone-level data migrations. All of it is verified against the
85
+ real viur-core (and the real envelope renderer) by the integration
86
+ suite. See [analysis/](analysis/README.md) for the design and
87
+ [CHANGELOG.md](CHANGELOG.md) for the running summary.
88
+
89
+ ## Requirements
90
+
91
+ - Python ≥ 3.12
92
+ - viur-core ≥ 3.8, < 4
93
+
94
+ ## Install
95
+
96
+ ```bash
97
+ pip install spltz-viur-models
98
+ ```
99
+
100
+ ## Quick taste
101
+
102
+ ```python
103
+ from viur.models import Country, Email, Text, Field, Model
104
+
105
+ class Feedback(Model, table=True): # a plain SQLModel underneath
106
+ name: str = Field(descr="Name", max_length=100)
107
+ mail: Email = Field(descr="E-Mail") # bone type "str.email"
108
+ message: Text = Field(default="") # bone type "text"
109
+ country: Country | None = Field(default=None) # "select.country"
110
+
111
+ Feedback.viur_structure() # skeleton-compatible structure dict
112
+ Feedback(id=42).viur_key # opaque key string, like a datastore key
113
+ ```
114
+
115
+ The bone type is decided by the **Python type** — `str`, `int`, `bool`,
116
+ `datetime`, `enum`/`Literal` map automatically; semantic types come from
117
+ the pydantic ecosystem (e.g. `Country` is pydantic-extra-types'
118
+ `CountryAlpha2`) or are one `Annotated` alias away:
119
+
120
+ ```python
121
+ Slug = t.Annotated[str, BoneType("str.slug")]
122
+ ```
123
+
124
+ The bone-by-bone mapping (skeleton declaration vs. field equivalent) is
125
+ documented in [docs/bones.md](docs/bones.md).
126
+
127
+ Serve a model like a skeleton module — same endpoints, same envelope-v2
128
+ wire format:
129
+
130
+ ```python
131
+ # deploy/modules/feedback.py
132
+ from viur.models.sqllist import SQLList
133
+ from models.feedback import Feedback
134
+
135
+ class feedback(SQLList):
136
+ model = Feedback
137
+
138
+ def can(self, instance): # fail-closed by default; open up per
139
+ return True # action via canView/canEdit/… overrides
140
+
141
+ # deploy/main.py — two calls, one either side of core.setup():
142
+ import viur.models
143
+
144
+ viur.models.install(engine="postgres", # NullPool on App Engine
145
+ postgres_dsn="postgresql+pg8000://…")
146
+ app = core.setup(modules, render)
147
+ viur.models.setup() # needs the models imported
148
+ ```
149
+
150
+ `install()` builds the engine and wires the cross-store refresh hooks;
151
+ `setup()` reports the schema state (and bootstraps it for the in-memory
152
+ preset). Details in [docs/getting-started.md](docs/getting-started.md).
153
+
154
+ ## Migrations
155
+
156
+ `create_all()` bootstraps a schema but never changes one — it silently
157
+ ignores every altered column, removed field and changed type. Install the
158
+ extra and let Alembic do it:
159
+
160
+ ```bash
161
+ pip install "spltz-viur-models[migrations]"
162
+ ```
163
+
164
+ The scaffold is generated on the dev server — one argument, no `alembic init`:
165
+
166
+ ```python
167
+ # deploy/main.py, after core.setup()
168
+ viur.models.setup(migrations=PROJECT_ROOT)
169
+ ```
170
+
171
+ It writes only what is missing (hand edits survive), is inert on a deployed
172
+ instance, and puts the first revision in place right away: a fresh database
173
+ gets `upgrade head`, one that already has the tables gets **stamped** with a
174
+ revision autogenerated against an empty probe database — so nothing is
175
+ dropped and the history still describes the full schema.
176
+
177
+ The result lives **next to** the distribution folder, not inside it — Alembic
178
+ is build-time tooling and has no business being uploaded with the app:
179
+
180
+ ```
181
+ myproject/
182
+ alembic.ini # prepend_sys_path = %(here)s/deploy
183
+ migrations/
184
+ env.py # 4 lines: import_models() + run()
185
+ versions/ # the revisions — commit these, they are code
186
+ deploy/ # what actually gets deployed
187
+ ```
188
+
189
+ ```bash
190
+ alembic revision --autogenerate -m "add slug to entry"
191
+ alembic upgrade head
192
+ alembic check # CI gate: do models and schema still agree?
193
+ ```
194
+
195
+ **Bone-level changes bring their own data migration.** A skeleton project
196
+ just edits the bone and viur-core coerces on read; in SQL the same change
197
+ moves data, and Alembic would drop the link table first. Eight transitions
198
+ are detected and generated — following viur-core's own rules:
199
+
200
+ ```python
201
+ def upgrade() -> None:
202
+ op.reduce_languages("post", "title", new_type=sa.String(200),
203
+ languages=["de", "en"], keep="de")
204
+ op.collapse_multiple("post", link_table="post_tag", target_column="tag_id",
205
+ link_parent_fk="post_id", link_dest_fk="tag_id",
206
+ foreign_table="tag", keep="first") # = loadVal[0]
207
+ ```
208
+
209
+ multiple ↔ single, multilingual ↔ plain, `str` ↔ `Text`, numeric precision,
210
+ `select` → `bool`, new/removed fields and new `using` payload columns. Only
211
+ `bool` → `select` needs a line from you — viur-core has no rule there, so
212
+ the generator emits a stub that refuses to run until the mapping is filled.
213
+ Detection runs on **structure snapshots**, not on Alembic's DDL diff, which
214
+ cannot see `str` → `Text` at all.
215
+
216
+ The module also handles what a stock Alembic scaffold does not: resolving
217
+ the database URL **without booting viur-core**, rendering custom
218
+ `TypeDecorator` columns (`RecordJSON`) as their DDL type so revisions stay
219
+ frozen snapshots, and SQLite's batch mode. Details in
220
+ [docs/migrations.md](docs/migrations.md), design rationale in
221
+ [analysis/03](analysis/03-migrations.md).
222
+
223
+ ## Development
224
+
225
+ Two test layers, run separately:
226
+
227
+ **Unit (fast, mocked) — `tests/`, 100 % coverage gate:**
228
+
229
+ ```bash
230
+ git clone https://github.com/sprengplatz/viur-models
231
+ cd viur-models
232
+ pip install --no-deps -e .
233
+ 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'
234
+ pytest # 100% coverage required
235
+ ```
236
+
237
+ `viur-light-mock` provides the `viur.core.*` stand-ins so these run without
238
+ the App Engine stack.
239
+
240
+ **Integration (real core) — `integration/`, no coverage gate:**
241
+
242
+ ```bash
243
+ pip install "viur-core>=3.8,<4" rsa pytest 'spltz-viur-actions>=0.4,<1.0' sqlmodel pydantic-extra-types pycountry email-validator
244
+ pip install --no-deps -e .
245
+ python -m pytest -c integration/pytest.ini integration
246
+ ```
247
+
248
+ Runs against the **real** framework — the layer that catches
249
+ mock-vs-core drift. See [integration/README.md](integration/README.md).
250
+
251
+ > **Coverage policy.** The 100 % gate applies to the **unit** layer only.
252
+ > The integration layer runs **without** a coverage requirement — a coverage
253
+ > target there would pressure mocking the very framework it exists to exercise.
254
+
255
+ ## Documentation
256
+
257
+ [sprengplatz.github.io/viur-models](https://sprengplatz.github.io/viur-models/)
258
+ — available in English and German (`/de/`).
259
+
260
+ ## License
261
+
262
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,195 @@
1
+ # viur-models
2
+
3
+ SQLModel definitions for ViUR applications.
4
+
5
+ [![Tests](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml/badge.svg)](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+
8
+ ## Status
9
+
10
+ **Alpha.** Implemented: `Field`, the bone-type field types
11
+ (`Text`, `Email`, `Country`, + `BoneType`/`register_bone_type` for your
12
+ own), the skeleton-compatible structure/dump emission, fromClient error
13
+ mapping, opaque key encoding — the **`SQLList` module prototype**
14
+ (list/view/add/edit/delete/structure over envelope v2, hooks from
15
+ viur-actions, session-per-action) — and **schema migrations**
16
+ (`viur.models.migrations`, Alembic) with automatically generated
17
+ bone-level data migrations. All of it is verified against the
18
+ real viur-core (and the real envelope renderer) by the integration
19
+ suite. See [analysis/](analysis/README.md) for the design and
20
+ [CHANGELOG.md](CHANGELOG.md) for the running summary.
21
+
22
+ ## Requirements
23
+
24
+ - Python ≥ 3.12
25
+ - viur-core ≥ 3.8, < 4
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install spltz-viur-models
31
+ ```
32
+
33
+ ## Quick taste
34
+
35
+ ```python
36
+ from viur.models import Country, Email, Text, Field, Model
37
+
38
+ class Feedback(Model, table=True): # a plain SQLModel underneath
39
+ name: str = Field(descr="Name", max_length=100)
40
+ mail: Email = Field(descr="E-Mail") # bone type "str.email"
41
+ message: Text = Field(default="") # bone type "text"
42
+ country: Country | None = Field(default=None) # "select.country"
43
+
44
+ Feedback.viur_structure() # skeleton-compatible structure dict
45
+ Feedback(id=42).viur_key # opaque key string, like a datastore key
46
+ ```
47
+
48
+ The bone type is decided by the **Python type** — `str`, `int`, `bool`,
49
+ `datetime`, `enum`/`Literal` map automatically; semantic types come from
50
+ the pydantic ecosystem (e.g. `Country` is pydantic-extra-types'
51
+ `CountryAlpha2`) or are one `Annotated` alias away:
52
+
53
+ ```python
54
+ Slug = t.Annotated[str, BoneType("str.slug")]
55
+ ```
56
+
57
+ The bone-by-bone mapping (skeleton declaration vs. field equivalent) is
58
+ documented in [docs/bones.md](docs/bones.md).
59
+
60
+ Serve a model like a skeleton module — same endpoints, same envelope-v2
61
+ wire format:
62
+
63
+ ```python
64
+ # deploy/modules/feedback.py
65
+ from viur.models.sqllist import SQLList
66
+ from models.feedback import Feedback
67
+
68
+ class feedback(SQLList):
69
+ model = Feedback
70
+
71
+ def can(self, instance): # fail-closed by default; open up per
72
+ return True # action via canView/canEdit/… overrides
73
+
74
+ # deploy/main.py — two calls, one either side of core.setup():
75
+ import viur.models
76
+
77
+ viur.models.install(engine="postgres", # NullPool on App Engine
78
+ postgres_dsn="postgresql+pg8000://…")
79
+ app = core.setup(modules, render)
80
+ viur.models.setup() # needs the models imported
81
+ ```
82
+
83
+ `install()` builds the engine and wires the cross-store refresh hooks;
84
+ `setup()` reports the schema state (and bootstraps it for the in-memory
85
+ preset). Details in [docs/getting-started.md](docs/getting-started.md).
86
+
87
+ ## Migrations
88
+
89
+ `create_all()` bootstraps a schema but never changes one — it silently
90
+ ignores every altered column, removed field and changed type. Install the
91
+ extra and let Alembic do it:
92
+
93
+ ```bash
94
+ pip install "spltz-viur-models[migrations]"
95
+ ```
96
+
97
+ The scaffold is generated on the dev server — one argument, no `alembic init`:
98
+
99
+ ```python
100
+ # deploy/main.py, after core.setup()
101
+ viur.models.setup(migrations=PROJECT_ROOT)
102
+ ```
103
+
104
+ It writes only what is missing (hand edits survive), is inert on a deployed
105
+ instance, and puts the first revision in place right away: a fresh database
106
+ gets `upgrade head`, one that already has the tables gets **stamped** with a
107
+ revision autogenerated against an empty probe database — so nothing is
108
+ dropped and the history still describes the full schema.
109
+
110
+ The result lives **next to** the distribution folder, not inside it — Alembic
111
+ is build-time tooling and has no business being uploaded with the app:
112
+
113
+ ```
114
+ myproject/
115
+ alembic.ini # prepend_sys_path = %(here)s/deploy
116
+ migrations/
117
+ env.py # 4 lines: import_models() + run()
118
+ versions/ # the revisions — commit these, they are code
119
+ deploy/ # what actually gets deployed
120
+ ```
121
+
122
+ ```bash
123
+ alembic revision --autogenerate -m "add slug to entry"
124
+ alembic upgrade head
125
+ alembic check # CI gate: do models and schema still agree?
126
+ ```
127
+
128
+ **Bone-level changes bring their own data migration.** A skeleton project
129
+ just edits the bone and viur-core coerces on read; in SQL the same change
130
+ moves data, and Alembic would drop the link table first. Eight transitions
131
+ are detected and generated — following viur-core's own rules:
132
+
133
+ ```python
134
+ def upgrade() -> None:
135
+ op.reduce_languages("post", "title", new_type=sa.String(200),
136
+ languages=["de", "en"], keep="de")
137
+ op.collapse_multiple("post", link_table="post_tag", target_column="tag_id",
138
+ link_parent_fk="post_id", link_dest_fk="tag_id",
139
+ foreign_table="tag", keep="first") # = loadVal[0]
140
+ ```
141
+
142
+ multiple ↔ single, multilingual ↔ plain, `str` ↔ `Text`, numeric precision,
143
+ `select` → `bool`, new/removed fields and new `using` payload columns. Only
144
+ `bool` → `select` needs a line from you — viur-core has no rule there, so
145
+ the generator emits a stub that refuses to run until the mapping is filled.
146
+ Detection runs on **structure snapshots**, not on Alembic's DDL diff, which
147
+ cannot see `str` → `Text` at all.
148
+
149
+ The module also handles what a stock Alembic scaffold does not: resolving
150
+ the database URL **without booting viur-core**, rendering custom
151
+ `TypeDecorator` columns (`RecordJSON`) as their DDL type so revisions stay
152
+ frozen snapshots, and SQLite's batch mode. Details in
153
+ [docs/migrations.md](docs/migrations.md), design rationale in
154
+ [analysis/03](analysis/03-migrations.md).
155
+
156
+ ## Development
157
+
158
+ Two test layers, run separately:
159
+
160
+ **Unit (fast, mocked) — `tests/`, 100 % coverage gate:**
161
+
162
+ ```bash
163
+ git clone https://github.com/sprengplatz/viur-models
164
+ cd viur-models
165
+ pip install --no-deps -e .
166
+ 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'
167
+ pytest # 100% coverage required
168
+ ```
169
+
170
+ `viur-light-mock` provides the `viur.core.*` stand-ins so these run without
171
+ the App Engine stack.
172
+
173
+ **Integration (real core) — `integration/`, no coverage gate:**
174
+
175
+ ```bash
176
+ pip install "viur-core>=3.8,<4" rsa pytest 'spltz-viur-actions>=0.4,<1.0' sqlmodel pydantic-extra-types pycountry email-validator
177
+ pip install --no-deps -e .
178
+ python -m pytest -c integration/pytest.ini integration
179
+ ```
180
+
181
+ Runs against the **real** framework — the layer that catches
182
+ mock-vs-core drift. See [integration/README.md](integration/README.md).
183
+
184
+ > **Coverage policy.** The 100 % gate applies to the **unit** layer only.
185
+ > The integration layer runs **without** a coverage requirement — a coverage
186
+ > target there would pressure mocking the very framework it exists to exercise.
187
+
188
+ ## Documentation
189
+
190
+ [sprengplatz.github.io/viur-models](https://sprengplatz.github.io/viur-models/)
191
+ — available in English and German (`/de/`).
192
+
193
+ ## License
194
+
195
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,101 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "spltz-viur-models"
7
+ dynamic = ["version"]
8
+ description = "SQLmodel definitions for ViUR applications."
9
+ readme = "README.md"
10
+ license = { file = "LICENSE" }
11
+ requires-python = ">=3.12"
12
+ authors = [
13
+ { name = "Andreas H. Kelch" },
14
+ ]
15
+ keywords = ["viur", "models", "skeletons", "bones"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "viur-core>=3.8,<4",
27
+ "spltz-viur-actions>=0.4,<1.0",
28
+ "sqlmodel>=0.0.39",
29
+ "pydantic-extra-types>=2.10",
30
+ "pycountry>=24.6",
31
+ "email-validator>=2",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ migrations = [
36
+ "alembic>=1.13",
37
+ ]
38
+ bigquery = [
39
+ "sqlalchemy-bigquery>=1.11",
40
+ ]
41
+ test = [
42
+ "pytest~=8.0",
43
+ "pytest-cov~=5.0",
44
+ "coverage[toml]~=7.0",
45
+ "spltz-viur-light-mock>=0.3,<1.0",
46
+ "alembic>=1.13",
47
+ ]
48
+ docs = [
49
+ "mkdocs-material~=9.5",
50
+ "mkdocstrings[python]~=0.26",
51
+ "mkdocs-static-i18n~=1.2",
52
+ ]
53
+ dev = [
54
+ "spltz-viur-models[migrations,bigquery,test,docs]",
55
+ "build~=1.2",
56
+ ]
57
+
58
+ [project.urls]
59
+ Homepage = "https://github.com/sprengplatz/viur-models"
60
+ Repository = "https://github.com/sprengplatz/viur-models.git"
61
+ "Bug Tracker" = "https://github.com/sprengplatz/viur-models/issues"
62
+ Changelog = "https://github.com/sprengplatz/viur-models/blob/main/CHANGELOG.md"
63
+
64
+ [tool.setuptools.dynamic]
65
+ version = { attr = "viur.models.__version__" }
66
+
67
+ [tool.setuptools.packages.find]
68
+ where = ["src"]
69
+ include = ["viur.*"]
70
+
71
+ [tool.pytest.ini_options]
72
+ minversion = "8.0"
73
+ testpaths = ["tests"]
74
+ addopts = [
75
+ "-ra",
76
+ "--strict-markers",
77
+ "--strict-config",
78
+ "--cov=viur.models",
79
+ "--cov-report=term-missing",
80
+ "--cov-report=xml",
81
+ "--cov-report=html",
82
+ "--cov-fail-under=100",
83
+ ]
84
+ filterwarnings = [
85
+ "error",
86
+ "ignore:'cgi' is deprecated and slated for removal in Python 3\\.13:DeprecationWarning",
87
+ ]
88
+
89
+ [tool.coverage.run]
90
+ branch = true
91
+ source = ["viur.models"]
92
+
93
+ [tool.coverage.report]
94
+ exclude_also = [
95
+ "raise NotImplementedError",
96
+ "if TYPE_CHECKING:",
97
+ "if t\\.TYPE_CHECKING:",
98
+ "\\.\\.\\.",
99
+ ]
100
+ show_missing = true
101
+ skip_covered = false
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+