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.
- spltz_viur_models-0.1.0/LICENSE +21 -0
- spltz_viur_models-0.1.0/PKG-INFO +262 -0
- spltz_viur_models-0.1.0/README.md +195 -0
- spltz_viur_models-0.1.0/pyproject.toml +101 -0
- spltz_viur_models-0.1.0/setup.cfg +4 -0
- spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/PKG-INFO +262 -0
- spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/SOURCES.txt +49 -0
- spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/dependency_links.txt +1 -0
- spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/requires.txt +28 -0
- spltz_viur_models-0.1.0/src/spltz_viur_models.egg-info/top_level.txt +1 -0
- spltz_viur_models-0.1.0/src/viur/models/__init__.py +91 -0
- spltz_viur_models-0.1.0/src/viur/models/base.py +572 -0
- spltz_viur_models-0.1.0/src/viur/models/bigquery.py +70 -0
- spltz_viur_models-0.1.0/src/viur/models/boot.py +199 -0
- spltz_viur_models-0.1.0/src/viur/models/client.py +35 -0
- spltz_viur_models-0.1.0/src/viur/models/config.py +36 -0
- spltz_viur_models-0.1.0/src/viur/models/crossstore.py +440 -0
- spltz_viur_models-0.1.0/src/viur/models/db.py +166 -0
- spltz_viur_models-0.1.0/src/viur/models/fields.py +95 -0
- spltz_viur_models-0.1.0/src/viur/models/links.py +6 -0
- spltz_viur_models-0.1.0/src/viur/models/migrate.py +622 -0
- spltz_viur_models-0.1.0/src/viur/models/migrations.py +538 -0
- spltz_viur_models-0.1.0/src/viur/models/scaffold.py +159 -0
- spltz_viur_models-0.1.0/src/viur/models/schema.py +382 -0
- spltz_viur_models-0.1.0/src/viur/models/sqllist.py +601 -0
- spltz_viur_models-0.1.0/src/viur/models/structure.py +645 -0
- spltz_viur_models-0.1.0/src/viur/models/types.py +202 -0
- spltz_viur_models-0.1.0/tests/test_base.py +44 -0
- spltz_viur_models-0.1.0/tests/test_bigquery.py +208 -0
- spltz_viur_models-0.1.0/tests/test_boot.py +382 -0
- spltz_viur_models-0.1.0/tests/test_client.py +96 -0
- spltz_viur_models-0.1.0/tests/test_computed.py +145 -0
- spltz_viur_models-0.1.0/tests/test_config.py +126 -0
- spltz_viur_models-0.1.0/tests/test_crossstore.py +813 -0
- spltz_viur_models-0.1.0/tests/test_db.py +71 -0
- spltz_viur_models-0.1.0/tests/test_fields.py +37 -0
- spltz_viur_models-0.1.0/tests/test_generator.py +725 -0
- spltz_viur_models-0.1.0/tests/test_golden.py +26 -0
- spltz_viur_models-0.1.0/tests/test_migrate.py +767 -0
- spltz_viur_models-0.1.0/tests/test_migrations.py +520 -0
- spltz_viur_models-0.1.0/tests/test_package.py +37 -0
- spltz_viur_models-0.1.0/tests/test_records.py +229 -0
- spltz_viur_models-0.1.0/tests/test_relations.py +533 -0
- spltz_viur_models-0.1.0/tests/test_scaffold.py +123 -0
- spltz_viur_models-0.1.0/tests/test_schema.py +428 -0
- spltz_viur_models-0.1.0/tests/test_sqllist.py +902 -0
- spltz_viur_models-0.1.0/tests/test_structure.py +188 -0
- spltz_viur_models-0.1.0/tests/test_suite_guard.py +21 -0
- spltz_viur_models-0.1.0/tests/test_types.py +60 -0
- spltz_viur_models-0.1.0/tests/test_using_relations.py +275 -0
- 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
|
+
[](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml)
|
|
73
|
+
[](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
|
+
[](https://github.com/sprengplatz/viur-models/actions/workflows/test.yml)
|
|
6
|
+
[](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
|