sqlmodel-ext 0.4.0__tar.gz → 0.4.2__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.
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/PKG-INFO +50 -29
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/README.md +42 -27
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/README_zh.md +40 -25
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/cached-table.md +20 -16
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/crud-pipeline.md +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/metaclass.md +375 -319
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/polymorphic-internals.md +24 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/relation-load-checker.md +33 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/cache-queries.md +143 -142
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/define-sti-models.md +132 -132
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/handle-concurrent-updates.md +94 -88
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/integrate-with-fastapi.md +10 -7
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/prevent-missing-greenlet.md +167 -162
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/index.md +4 -4
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/base-classes.md +22 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/crud-methods.md +7 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/field-types.md +4 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/index.md +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/mixins.md +35 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/pagination-types.md +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/tutorials/01-getting-started.md +6 -5
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/tutorials/02-building-a-blog-api.md +389 -388
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/tutorials/03-adding-redis-cache.md +277 -269
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/cached-table.md +16 -16
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/crud-pipeline.md +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/metaclass.md +351 -319
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/polymorphic-internals.md +13 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/relation-load-checker.md +21 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/cache-queries.md +143 -142
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/define-sti-models.md +132 -132
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/handle-concurrent-updates.md +93 -87
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/integrate-with-fastapi.md +10 -7
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/prevent-missing-greenlet.md +164 -162
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/index.md +4 -4
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/base-classes.md +19 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/crud-methods.md +5 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/field-types.md +4 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/index.md +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/mixins.md +30 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/pagination-types.md +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/tutorials/01-getting-started.md +6 -5
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/tutorials/02-building-a-blog-api.md +388 -388
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/tutorials/03-adding-redis-cache.md +277 -269
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/examples/consumer_integration/run.py +149 -148
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/pyproject.toml +8 -2
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/__init__.py +1 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/base.py +135 -3
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/__init__.py +53 -1
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/_internal/path.py +5 -1
- sqlmodel_ext-0.4.2/src/sqlmodel_ext/field_types/dialects/postgresql/array.py +218 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/table.py +70 -13
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/_models.py +69 -67
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_array_explicit_field_sa_type.py +18 -4
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_basic_crud.py +71 -61
- sqlmodel_ext-0.4.2/tests/test_cached_table.py +513 -0
- sqlmodel_ext-0.4.2/tests/test_crud_extended.py +582 -0
- sqlmodel_ext-0.4.2/tests/test_field_types_misc.py +189 -0
- sqlmodel_ext-0.4.2/tests/test_field_types_str_num.py +400 -0
- sqlmodel_ext-0.4.2/tests/test_field_types_url_ssrf.py +267 -0
- sqlmodel_ext-0.4.2/tests/test_metaclass_extra.py +194 -0
- sqlmodel_ext-0.4.2/tests/test_optimistic_lock.py +190 -0
- sqlmodel_ext-0.4.2/tests/test_pagination.py +259 -0
- sqlmodel_ext-0.4.2/tests/test_pg_dialect_types.py +427 -0
- sqlmodel_ext-0.4.2/tests/test_polymorphic_jti.py +273 -0
- sqlmodel_ext-0.4.2/tests/test_polymorphic_sti_query.py +418 -0
- sqlmodel_ext-0.4.2/tests/test_relation_load_checker.py +232 -0
- sqlmodel_ext-0.4.2/tests/test_relation_preload.py +240 -0
- sqlmodel_ext-0.4.2/tests/test_relationship_pep604.py +115 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/uv.lock +2 -2
- sqlmodel_ext-0.4.0/src/sqlmodel_ext/field_types/dialects/postgresql/array.py +0 -119
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/.github/workflows/ci.yml +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/.gitignore +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/LICENSE +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/.vitepress/config.mts +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/.vitepress/en.mts +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/.vitepress/theme/index.ts +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/.vitepress/theme/style/custom.css +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/.vitepress/zh.mts +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/cascade-delete-semantics.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/optimistic-lock.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/prerequisites.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/explanation/relation-preload.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/cached-table.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/crud.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/field-types.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/optimistic-lock.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/pagination.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/polymorphic.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/relation-load-checker.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/guide/relation-preload.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/configure-cascade-delete.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/define-jti-models.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/paginate-a-list-endpoint.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/how-to/release-connection-during-long-io.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/cached-table.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/crud.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/metaclass.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/optimistic-lock.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/polymorphic.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/prerequisites.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/relation-load-checker.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/internals/relation-preload.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/reference/decorators.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/en/tutorials/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/cascade-delete-semantics.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/optimistic-lock.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/prerequisites.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/explanation/relation-preload.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/cached-table.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/crud.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/field-types.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/optimistic-lock.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/pagination.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/polymorphic.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/relation-load-checker.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/guide/relation-preload.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/configure-cascade-delete.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/define-jti-models.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/paginate-a-list-endpoint.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/how-to/release-connection-during-long-io.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/cached-table.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/crud.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/metaclass.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/optimistic-lock.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/polymorphic.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/prerequisites.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/relation-load-checker.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/internals/relation-preload.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/reference/decorators.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/docs/tutorials/index.md +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/package.json +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/pnpm-lock.yaml +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/_compat.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/_exceptions.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/_sa_type.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/_utils.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/_internal/__init__.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/_ssrf.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/dialects/__init__.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/dialects/postgresql/__init__.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/dialects/postgresql/exceptions.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/dialects/postgresql/jsonb_types.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/dialects/postgresql/numpy_vector.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/ip_address.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/mixins/__init__.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/mixins/module_name_mixin.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/field_types/url.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/__init__.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/cached_table.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/info_response.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/optimistic_lock.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/polymorphic.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/mixins/relation_preload.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/pagination.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/relation_load_checker.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/src/sqlmodel_ext/session.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/__init__.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/conftest.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_all_fields_optional_constraints.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_annotated_default_factory_inheritance.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_decimal_types.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_deferred_index.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_enhanced_session.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_smoke_imports.py +0 -0
- {sqlmodel_ext-0.4.0 → sqlmodel_ext-0.4.2}/tests/test_sti_default_isolation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlmodel-ext
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Extended SQLModel infrastructure: smart metaclass, async CRUD mixins, polymorphic inheritance, optimistic locking, relation preloading, and reusable field types.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Foxerine/sqlmodel-ext
|
|
6
6
|
Project-URL: Repository, https://github.com/Foxerine/sqlmodel-ext
|
|
@@ -24,14 +24,20 @@ Classifier: Typing :: Typed
|
|
|
24
24
|
Requires-Python: >=3.12
|
|
25
25
|
Requires-Dist: pydantic>=2.0
|
|
26
26
|
Requires-Dist: sqlalchemy>=2.0
|
|
27
|
-
Requires-Dist: sqlmodel>=0.0.
|
|
27
|
+
Requires-Dist: sqlmodel>=0.0.32
|
|
28
28
|
Provides-Extra: cache
|
|
29
29
|
Requires-Dist: orjson>=3.0; extra == 'cache'
|
|
30
30
|
Requires-Dist: redis>=5.0; extra == 'cache'
|
|
31
31
|
Provides-Extra: dev
|
|
32
32
|
Requires-Dist: aiosqlite>=0.20; extra == 'dev'
|
|
33
|
+
Requires-Dist: fakeredis>=2.20; extra == 'dev'
|
|
34
|
+
Requires-Dist: fastapi>=0.100.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: numpy>=1.24; extra == 'dev'
|
|
36
|
+
Requires-Dist: orjson>=3.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: pgvector>=0.3; extra == 'dev'
|
|
33
38
|
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
34
39
|
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
40
|
+
Requires-Dist: redis>=5.0; extra == 'dev'
|
|
35
41
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
36
42
|
Provides-Extra: fastapi
|
|
37
43
|
Requires-Dist: fastapi>=0.100.0; extra == 'fastapi'
|
|
@@ -70,7 +76,7 @@ Extended SQLModel infrastructure: smart metaclass, async CRUD mixins, polymorphi
|
|
|
70
76
|
| **RelationPreloadMixin** | Decorator-based automatic relationship preloading (prevents `MissingGreenlet` errors) |
|
|
71
77
|
| **ListResponse[T]** | Generic paginated response model for list endpoints |
|
|
72
78
|
| **Field Types** | Reusable constrained types: `Str64`, `Port`, `IPAddress`, `HttpUrl`, `SafeHttpUrl`, and more |
|
|
73
|
-
| **PostgreSQL Types** | `Array[T]` for native ARRAY, `JSON100K`/`JSONList100K` for size-limited JSONB, `NumpyVector` for pgvector+NumPy |
|
|
79
|
+
| **PostgreSQL Types** | `Array[T]` for native ARRAY (read-tolerant for enum arrays during rolling-deploy version skew), `JSON100K`/`JSONList100K` for size-limited JSONB, `NumpyVector` for pgvector+NumPy |
|
|
74
80
|
| **Info Response DTOs** | Pre-built mixins for API response models with id/timestamp fields |
|
|
75
81
|
|
|
76
82
|
## Installation
|
|
@@ -108,12 +114,13 @@ pip install sqlmodel-ext[pgvector]
|
|
|
108
114
|
### Define Models
|
|
109
115
|
|
|
110
116
|
```python
|
|
111
|
-
from
|
|
117
|
+
from pydantic import EmailStr # requires: pip install 'pydantic[email]'
|
|
118
|
+
from sqlmodel_ext import SQLModelBase, UUIDTableBaseMixin, NonEmptyStrippedStr64
|
|
112
119
|
|
|
113
120
|
# Base class -- fields only, no database table
|
|
114
121
|
class UserBase(SQLModelBase):
|
|
115
|
-
name:
|
|
116
|
-
email:
|
|
122
|
+
name: NonEmptyStrippedStr64 # user-visible name: rejects "" and whitespace-only
|
|
123
|
+
email: EmailStr
|
|
117
124
|
|
|
118
125
|
# Table class -- inherits fields + gains async CRUD + UUID primary key
|
|
119
126
|
class User(UserBase, UUIDTableBaseMixin, table=True):
|
|
@@ -438,17 +445,19 @@ async def delete_article(session: SessionDep, article_id: UUID) -> None:
|
|
|
438
445
|
|
|
439
446
|
```python
|
|
440
447
|
from abc import ABC, abstractmethod
|
|
448
|
+
from pydantic import EmailStr
|
|
441
449
|
from sqlmodel_ext import (
|
|
442
450
|
SQLModelBase, UUIDTableBaseMixin, PolymorphicBaseMixin,
|
|
443
451
|
AutoPolymorphicIdentityMixin, create_subclass_id_mixin,
|
|
444
452
|
ListResponse, TableViewRequest,
|
|
453
|
+
Str512, Text1K,
|
|
445
454
|
)
|
|
446
455
|
|
|
447
456
|
# ── Polymorphic models ────────────────────────────────────────────
|
|
448
457
|
|
|
449
458
|
class NotificationBase(SQLModelBase):
|
|
450
459
|
user_id: UUID = Field(foreign_key='user.id')
|
|
451
|
-
message:
|
|
460
|
+
message: Text1K
|
|
452
461
|
|
|
453
462
|
class Notification(NotificationBase, UUIDTableBaseMixin, PolymorphicBaseMixin, ABC):
|
|
454
463
|
@abstractmethod
|
|
@@ -457,13 +466,13 @@ class Notification(NotificationBase, UUIDTableBaseMixin, PolymorphicBaseMixin, A
|
|
|
457
466
|
NotifSubclassId = create_subclass_id_mixin('notification')
|
|
458
467
|
|
|
459
468
|
class EmailNotification(NotifSubclassId, Notification, AutoPolymorphicIdentityMixin, table=True):
|
|
460
|
-
email_to:
|
|
469
|
+
email_to: EmailStr
|
|
461
470
|
|
|
462
471
|
def summary(self) -> str:
|
|
463
472
|
return f"Email to {self.email_to}: {self.message}"
|
|
464
473
|
|
|
465
474
|
class PushNotification(NotifSubclassId, Notification, AutoPolymorphicIdentityMixin, table=True):
|
|
466
|
-
device_token:
|
|
475
|
+
device_token: Str512
|
|
467
476
|
|
|
468
477
|
def summary(self) -> str:
|
|
469
478
|
return f"Push to {self.device_token}: {self.message}"
|
|
@@ -493,15 +502,15 @@ These mixins provide the async CRUD interface. `TableBaseMixin` uses an auto-inc
|
|
|
493
502
|
Both mixins automatically add `id`, `created_at`, and `updated_at` fields.
|
|
494
503
|
|
|
495
504
|
```python
|
|
496
|
-
from sqlmodel_ext import SQLModelBase, TableBaseMixin, UUIDTableBaseMixin
|
|
505
|
+
from sqlmodel_ext import SQLModelBase, TableBaseMixin, UUIDTableBaseMixin, NonEmptyStrippedStr64, Text1K
|
|
497
506
|
|
|
498
507
|
# Integer primary key
|
|
499
508
|
class LogEntry(SQLModelBase, TableBaseMixin, table=True):
|
|
500
|
-
message:
|
|
509
|
+
message: Text1K
|
|
501
510
|
|
|
502
511
|
# UUID primary key (recommended for most use cases)
|
|
503
512
|
class Project(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
504
|
-
name:
|
|
513
|
+
name: NonEmptyStrippedStr64
|
|
505
514
|
```
|
|
506
515
|
|
|
507
516
|
#### `add()` -- Batch Insert
|
|
@@ -529,7 +538,9 @@ user = await User.add(session, User(name="Alice", email="a@x.com"))
|
|
|
529
538
|
user = await user.save(session)
|
|
530
539
|
|
|
531
540
|
# Save with relationship preloading
|
|
532
|
-
|
|
541
|
+
# (rel() casts the Relationship field for type checkers, which would
|
|
542
|
+
# otherwise infer User.profile as Profile instead of a loadable attribute)
|
|
543
|
+
user = await user.save(session, load=rel(User.profile))
|
|
533
544
|
|
|
534
545
|
# Save with optimistic lock retry
|
|
535
546
|
user = await user.save(session, optimistic_retry_count=3)
|
|
@@ -562,9 +573,10 @@ user3 = await user3.save(session) # commits all three
|
|
|
562
573
|
#### `update()` -- Partial Update from a Model
|
|
563
574
|
|
|
564
575
|
```python
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
576
|
+
# all_fields_optional auto-converts every inherited field to ``T | None = None``
|
|
577
|
+
# while preserving constraints -- no hand-written Optional boilerplate
|
|
578
|
+
class UserUpdate(UserBase, all_fields_optional=True):
|
|
579
|
+
pass
|
|
568
580
|
|
|
569
581
|
# Only updates fields that were explicitly set
|
|
570
582
|
user = await user.update(session, UserUpdate(name="Charlie"))
|
|
@@ -643,7 +655,7 @@ users = await User.get(session, fetch_mode="all")
|
|
|
643
655
|
user = await User.get(
|
|
644
656
|
session,
|
|
645
657
|
User.id == user_id,
|
|
646
|
-
load=[User.profile, User.orders],
|
|
658
|
+
load=[rel(User.profile), rel(User.orders)],
|
|
647
659
|
)
|
|
648
660
|
|
|
649
661
|
# With JOIN
|
|
@@ -767,11 +779,12 @@ from sqlmodel_ext import (
|
|
|
767
779
|
SQLModelBase, UUIDTableBaseMixin,
|
|
768
780
|
PolymorphicBaseMixin, AutoPolymorphicIdentityMixin,
|
|
769
781
|
create_subclass_id_mixin,
|
|
782
|
+
HttpUrl, NonEmptyStrippedStr64, NonNegativeInt,
|
|
770
783
|
)
|
|
771
784
|
|
|
772
785
|
# 1. Base class (fields only, no table)
|
|
773
786
|
class ToolBase(SQLModelBase):
|
|
774
|
-
name:
|
|
787
|
+
name: NonEmptyStrippedStr64
|
|
775
788
|
|
|
776
789
|
# 2. Abstract parent (creates the parent table)
|
|
777
790
|
class Tool(ToolBase, UUIDTableBaseMixin, PolymorphicBaseMixin, ABC):
|
|
@@ -783,13 +796,13 @@ ToolSubclassIdMixin = create_subclass_id_mixin('tool')
|
|
|
783
796
|
|
|
784
797
|
# 4. Concrete subclasses (each gets its own table)
|
|
785
798
|
class WebSearchTool(ToolSubclassIdMixin, Tool, AutoPolymorphicIdentityMixin, table=True):
|
|
786
|
-
search_url:
|
|
799
|
+
search_url: HttpUrl
|
|
787
800
|
|
|
788
801
|
async def execute(self) -> str:
|
|
789
802
|
return f"Searching {self.search_url}"
|
|
790
803
|
|
|
791
804
|
class CalculatorTool(ToolSubclassIdMixin, Tool, AutoPolymorphicIdentityMixin, table=True):
|
|
792
|
-
precision:
|
|
805
|
+
precision: NonNegativeInt = 2
|
|
793
806
|
|
|
794
807
|
async def execute(self) -> str:
|
|
795
808
|
return "Calculating..."
|
|
@@ -823,16 +836,17 @@ from sqlmodel_ext import (
|
|
|
823
836
|
PolymorphicBaseMixin, AutoPolymorphicIdentityMixin,
|
|
824
837
|
register_sti_columns_for_all_subclasses,
|
|
825
838
|
register_sti_column_properties_for_all_subclasses,
|
|
839
|
+
NonNegativeBigInt, Str256,
|
|
826
840
|
)
|
|
827
841
|
|
|
828
842
|
class UserFile(SQLModelBase, UUIDTableBaseMixin, PolymorphicBaseMixin, table=True):
|
|
829
|
-
filename:
|
|
843
|
+
filename: Str256
|
|
830
844
|
|
|
831
845
|
class PendingFile(UserFile, AutoPolymorphicIdentityMixin, table=True):
|
|
832
846
|
upload_deadline: datetime | None = None # Added to userfile table as nullable
|
|
833
847
|
|
|
834
848
|
class CompletedFile(UserFile, AutoPolymorphicIdentityMixin, table=True):
|
|
835
|
-
file_size:
|
|
849
|
+
file_size: NonNegativeBigInt | None = None # Added to userfile table as nullable
|
|
836
850
|
|
|
837
851
|
# After all models are defined, before configure_mappers():
|
|
838
852
|
register_sti_columns_for_all_subclasses()
|
|
@@ -858,7 +872,7 @@ class ToolSet(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
|
858
872
|
tool_set = await ToolSet.get(
|
|
859
873
|
session,
|
|
860
874
|
ToolSet.id == ts_id,
|
|
861
|
-
load=ToolSet.tools,
|
|
875
|
+
load=rel(ToolSet.tools),
|
|
862
876
|
jti_subclasses='all', # Loads all subclass-specific columns
|
|
863
877
|
)
|
|
864
878
|
```
|
|
@@ -885,15 +899,22 @@ Tool._is_joined_table_inheritance() # True for JTI, False for STI
|
|
|
885
899
|
Prevents lost updates in concurrent environments using SQLAlchemy's `version_id_col` mechanism.
|
|
886
900
|
|
|
887
901
|
```python
|
|
902
|
+
from enum import StrEnum
|
|
903
|
+
|
|
888
904
|
from sqlmodel_ext import (
|
|
889
905
|
SQLModelBase, UUIDTableBaseMixin,
|
|
890
906
|
OptimisticLockMixin, OptimisticLockError,
|
|
907
|
+
NonNegativeDecimal38_18,
|
|
891
908
|
)
|
|
892
909
|
|
|
910
|
+
class OrderStatusEnum(StrEnum):
|
|
911
|
+
pending = 'pending'
|
|
912
|
+
paid = 'paid'
|
|
913
|
+
|
|
893
914
|
# OptimisticLockMixin MUST come before TableBaseMixin in MRO
|
|
894
915
|
class Order(OptimisticLockMixin, UUIDTableBaseMixin, table=True):
|
|
895
|
-
status:
|
|
896
|
-
amount:
|
|
916
|
+
status: OrderStatusEnum = OrderStatusEnum.pending
|
|
917
|
+
amount: NonNegativeDecimal38_18
|
|
897
918
|
```
|
|
898
919
|
|
|
899
920
|
The mixin adds a `version` integer field (starting at 0). Every `UPDATE` generates SQL like:
|
|
@@ -945,11 +966,11 @@ The `RelationPreloadMixin` and `@requires_relations` decorator automatically loa
|
|
|
945
966
|
|
|
946
967
|
```python
|
|
947
968
|
from sqlmodel import Relationship
|
|
948
|
-
from sqlmodel_ext import UUIDTableBaseMixin, SQLModelBase
|
|
969
|
+
from sqlmodel_ext import UUIDTableBaseMixin, SQLModelBase, NonNegativeDecimal38_18
|
|
949
970
|
from sqlmodel_ext.mixins import RelationPreloadMixin, requires_relations
|
|
950
971
|
|
|
951
972
|
class GeneratorConfig(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
952
|
-
price:
|
|
973
|
+
price: NonNegativeDecimal38_18
|
|
953
974
|
|
|
954
975
|
class Generator(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
955
976
|
config: GeneratorConfig = Relationship()
|
|
@@ -1391,12 +1412,12 @@ When using Pydantic's `use_attribute_docstrings=True` (enabled by default in `SQ
|
|
|
1391
1412
|
|
|
1392
1413
|
```python
|
|
1393
1414
|
class UserBase(SQLModelBase):
|
|
1394
|
-
name:
|
|
1415
|
+
name: NonEmptyStrippedStr64
|
|
1395
1416
|
"""User display name""" # ← parsed by Pydantic
|
|
1396
1417
|
|
|
1397
1418
|
class UserUpdateRequest(UserBase, all_fields_optional=True):
|
|
1398
1419
|
pass
|
|
1399
|
-
# name:
|
|
1420
|
+
# name: NonEmptyStrippedStr64 | None = None — description "User display name" is inherited
|
|
1400
1421
|
# Shows up correctly in OpenAPI/Swagger docs
|
|
1401
1422
|
```
|
|
1402
1423
|
|
|
@@ -25,7 +25,7 @@ Extended SQLModel infrastructure: smart metaclass, async CRUD mixins, polymorphi
|
|
|
25
25
|
| **RelationPreloadMixin** | Decorator-based automatic relationship preloading (prevents `MissingGreenlet` errors) |
|
|
26
26
|
| **ListResponse[T]** | Generic paginated response model for list endpoints |
|
|
27
27
|
| **Field Types** | Reusable constrained types: `Str64`, `Port`, `IPAddress`, `HttpUrl`, `SafeHttpUrl`, and more |
|
|
28
|
-
| **PostgreSQL Types** | `Array[T]` for native ARRAY, `JSON100K`/`JSONList100K` for size-limited JSONB, `NumpyVector` for pgvector+NumPy |
|
|
28
|
+
| **PostgreSQL Types** | `Array[T]` for native ARRAY (read-tolerant for enum arrays during rolling-deploy version skew), `JSON100K`/`JSONList100K` for size-limited JSONB, `NumpyVector` for pgvector+NumPy |
|
|
29
29
|
| **Info Response DTOs** | Pre-built mixins for API response models with id/timestamp fields |
|
|
30
30
|
|
|
31
31
|
## Installation
|
|
@@ -63,12 +63,13 @@ pip install sqlmodel-ext[pgvector]
|
|
|
63
63
|
### Define Models
|
|
64
64
|
|
|
65
65
|
```python
|
|
66
|
-
from
|
|
66
|
+
from pydantic import EmailStr # requires: pip install 'pydantic[email]'
|
|
67
|
+
from sqlmodel_ext import SQLModelBase, UUIDTableBaseMixin, NonEmptyStrippedStr64
|
|
67
68
|
|
|
68
69
|
# Base class -- fields only, no database table
|
|
69
70
|
class UserBase(SQLModelBase):
|
|
70
|
-
name:
|
|
71
|
-
email:
|
|
71
|
+
name: NonEmptyStrippedStr64 # user-visible name: rejects "" and whitespace-only
|
|
72
|
+
email: EmailStr
|
|
72
73
|
|
|
73
74
|
# Table class -- inherits fields + gains async CRUD + UUID primary key
|
|
74
75
|
class User(UserBase, UUIDTableBaseMixin, table=True):
|
|
@@ -393,17 +394,19 @@ async def delete_article(session: SessionDep, article_id: UUID) -> None:
|
|
|
393
394
|
|
|
394
395
|
```python
|
|
395
396
|
from abc import ABC, abstractmethod
|
|
397
|
+
from pydantic import EmailStr
|
|
396
398
|
from sqlmodel_ext import (
|
|
397
399
|
SQLModelBase, UUIDTableBaseMixin, PolymorphicBaseMixin,
|
|
398
400
|
AutoPolymorphicIdentityMixin, create_subclass_id_mixin,
|
|
399
401
|
ListResponse, TableViewRequest,
|
|
402
|
+
Str512, Text1K,
|
|
400
403
|
)
|
|
401
404
|
|
|
402
405
|
# ── Polymorphic models ────────────────────────────────────────────
|
|
403
406
|
|
|
404
407
|
class NotificationBase(SQLModelBase):
|
|
405
408
|
user_id: UUID = Field(foreign_key='user.id')
|
|
406
|
-
message:
|
|
409
|
+
message: Text1K
|
|
407
410
|
|
|
408
411
|
class Notification(NotificationBase, UUIDTableBaseMixin, PolymorphicBaseMixin, ABC):
|
|
409
412
|
@abstractmethod
|
|
@@ -412,13 +415,13 @@ class Notification(NotificationBase, UUIDTableBaseMixin, PolymorphicBaseMixin, A
|
|
|
412
415
|
NotifSubclassId = create_subclass_id_mixin('notification')
|
|
413
416
|
|
|
414
417
|
class EmailNotification(NotifSubclassId, Notification, AutoPolymorphicIdentityMixin, table=True):
|
|
415
|
-
email_to:
|
|
418
|
+
email_to: EmailStr
|
|
416
419
|
|
|
417
420
|
def summary(self) -> str:
|
|
418
421
|
return f"Email to {self.email_to}: {self.message}"
|
|
419
422
|
|
|
420
423
|
class PushNotification(NotifSubclassId, Notification, AutoPolymorphicIdentityMixin, table=True):
|
|
421
|
-
device_token:
|
|
424
|
+
device_token: Str512
|
|
422
425
|
|
|
423
426
|
def summary(self) -> str:
|
|
424
427
|
return f"Push to {self.device_token}: {self.message}"
|
|
@@ -448,15 +451,15 @@ These mixins provide the async CRUD interface. `TableBaseMixin` uses an auto-inc
|
|
|
448
451
|
Both mixins automatically add `id`, `created_at`, and `updated_at` fields.
|
|
449
452
|
|
|
450
453
|
```python
|
|
451
|
-
from sqlmodel_ext import SQLModelBase, TableBaseMixin, UUIDTableBaseMixin
|
|
454
|
+
from sqlmodel_ext import SQLModelBase, TableBaseMixin, UUIDTableBaseMixin, NonEmptyStrippedStr64, Text1K
|
|
452
455
|
|
|
453
456
|
# Integer primary key
|
|
454
457
|
class LogEntry(SQLModelBase, TableBaseMixin, table=True):
|
|
455
|
-
message:
|
|
458
|
+
message: Text1K
|
|
456
459
|
|
|
457
460
|
# UUID primary key (recommended for most use cases)
|
|
458
461
|
class Project(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
459
|
-
name:
|
|
462
|
+
name: NonEmptyStrippedStr64
|
|
460
463
|
```
|
|
461
464
|
|
|
462
465
|
#### `add()` -- Batch Insert
|
|
@@ -484,7 +487,9 @@ user = await User.add(session, User(name="Alice", email="a@x.com"))
|
|
|
484
487
|
user = await user.save(session)
|
|
485
488
|
|
|
486
489
|
# Save with relationship preloading
|
|
487
|
-
|
|
490
|
+
# (rel() casts the Relationship field for type checkers, which would
|
|
491
|
+
# otherwise infer User.profile as Profile instead of a loadable attribute)
|
|
492
|
+
user = await user.save(session, load=rel(User.profile))
|
|
488
493
|
|
|
489
494
|
# Save with optimistic lock retry
|
|
490
495
|
user = await user.save(session, optimistic_retry_count=3)
|
|
@@ -517,9 +522,10 @@ user3 = await user3.save(session) # commits all three
|
|
|
517
522
|
#### `update()` -- Partial Update from a Model
|
|
518
523
|
|
|
519
524
|
```python
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
525
|
+
# all_fields_optional auto-converts every inherited field to ``T | None = None``
|
|
526
|
+
# while preserving constraints -- no hand-written Optional boilerplate
|
|
527
|
+
class UserUpdate(UserBase, all_fields_optional=True):
|
|
528
|
+
pass
|
|
523
529
|
|
|
524
530
|
# Only updates fields that were explicitly set
|
|
525
531
|
user = await user.update(session, UserUpdate(name="Charlie"))
|
|
@@ -598,7 +604,7 @@ users = await User.get(session, fetch_mode="all")
|
|
|
598
604
|
user = await User.get(
|
|
599
605
|
session,
|
|
600
606
|
User.id == user_id,
|
|
601
|
-
load=[User.profile, User.orders],
|
|
607
|
+
load=[rel(User.profile), rel(User.orders)],
|
|
602
608
|
)
|
|
603
609
|
|
|
604
610
|
# With JOIN
|
|
@@ -722,11 +728,12 @@ from sqlmodel_ext import (
|
|
|
722
728
|
SQLModelBase, UUIDTableBaseMixin,
|
|
723
729
|
PolymorphicBaseMixin, AutoPolymorphicIdentityMixin,
|
|
724
730
|
create_subclass_id_mixin,
|
|
731
|
+
HttpUrl, NonEmptyStrippedStr64, NonNegativeInt,
|
|
725
732
|
)
|
|
726
733
|
|
|
727
734
|
# 1. Base class (fields only, no table)
|
|
728
735
|
class ToolBase(SQLModelBase):
|
|
729
|
-
name:
|
|
736
|
+
name: NonEmptyStrippedStr64
|
|
730
737
|
|
|
731
738
|
# 2. Abstract parent (creates the parent table)
|
|
732
739
|
class Tool(ToolBase, UUIDTableBaseMixin, PolymorphicBaseMixin, ABC):
|
|
@@ -738,13 +745,13 @@ ToolSubclassIdMixin = create_subclass_id_mixin('tool')
|
|
|
738
745
|
|
|
739
746
|
# 4. Concrete subclasses (each gets its own table)
|
|
740
747
|
class WebSearchTool(ToolSubclassIdMixin, Tool, AutoPolymorphicIdentityMixin, table=True):
|
|
741
|
-
search_url:
|
|
748
|
+
search_url: HttpUrl
|
|
742
749
|
|
|
743
750
|
async def execute(self) -> str:
|
|
744
751
|
return f"Searching {self.search_url}"
|
|
745
752
|
|
|
746
753
|
class CalculatorTool(ToolSubclassIdMixin, Tool, AutoPolymorphicIdentityMixin, table=True):
|
|
747
|
-
precision:
|
|
754
|
+
precision: NonNegativeInt = 2
|
|
748
755
|
|
|
749
756
|
async def execute(self) -> str:
|
|
750
757
|
return "Calculating..."
|
|
@@ -778,16 +785,17 @@ from sqlmodel_ext import (
|
|
|
778
785
|
PolymorphicBaseMixin, AutoPolymorphicIdentityMixin,
|
|
779
786
|
register_sti_columns_for_all_subclasses,
|
|
780
787
|
register_sti_column_properties_for_all_subclasses,
|
|
788
|
+
NonNegativeBigInt, Str256,
|
|
781
789
|
)
|
|
782
790
|
|
|
783
791
|
class UserFile(SQLModelBase, UUIDTableBaseMixin, PolymorphicBaseMixin, table=True):
|
|
784
|
-
filename:
|
|
792
|
+
filename: Str256
|
|
785
793
|
|
|
786
794
|
class PendingFile(UserFile, AutoPolymorphicIdentityMixin, table=True):
|
|
787
795
|
upload_deadline: datetime | None = None # Added to userfile table as nullable
|
|
788
796
|
|
|
789
797
|
class CompletedFile(UserFile, AutoPolymorphicIdentityMixin, table=True):
|
|
790
|
-
file_size:
|
|
798
|
+
file_size: NonNegativeBigInt | None = None # Added to userfile table as nullable
|
|
791
799
|
|
|
792
800
|
# After all models are defined, before configure_mappers():
|
|
793
801
|
register_sti_columns_for_all_subclasses()
|
|
@@ -813,7 +821,7 @@ class ToolSet(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
|
813
821
|
tool_set = await ToolSet.get(
|
|
814
822
|
session,
|
|
815
823
|
ToolSet.id == ts_id,
|
|
816
|
-
load=ToolSet.tools,
|
|
824
|
+
load=rel(ToolSet.tools),
|
|
817
825
|
jti_subclasses='all', # Loads all subclass-specific columns
|
|
818
826
|
)
|
|
819
827
|
```
|
|
@@ -840,15 +848,22 @@ Tool._is_joined_table_inheritance() # True for JTI, False for STI
|
|
|
840
848
|
Prevents lost updates in concurrent environments using SQLAlchemy's `version_id_col` mechanism.
|
|
841
849
|
|
|
842
850
|
```python
|
|
851
|
+
from enum import StrEnum
|
|
852
|
+
|
|
843
853
|
from sqlmodel_ext import (
|
|
844
854
|
SQLModelBase, UUIDTableBaseMixin,
|
|
845
855
|
OptimisticLockMixin, OptimisticLockError,
|
|
856
|
+
NonNegativeDecimal38_18,
|
|
846
857
|
)
|
|
847
858
|
|
|
859
|
+
class OrderStatusEnum(StrEnum):
|
|
860
|
+
pending = 'pending'
|
|
861
|
+
paid = 'paid'
|
|
862
|
+
|
|
848
863
|
# OptimisticLockMixin MUST come before TableBaseMixin in MRO
|
|
849
864
|
class Order(OptimisticLockMixin, UUIDTableBaseMixin, table=True):
|
|
850
|
-
status:
|
|
851
|
-
amount:
|
|
865
|
+
status: OrderStatusEnum = OrderStatusEnum.pending
|
|
866
|
+
amount: NonNegativeDecimal38_18
|
|
852
867
|
```
|
|
853
868
|
|
|
854
869
|
The mixin adds a `version` integer field (starting at 0). Every `UPDATE` generates SQL like:
|
|
@@ -900,11 +915,11 @@ The `RelationPreloadMixin` and `@requires_relations` decorator automatically loa
|
|
|
900
915
|
|
|
901
916
|
```python
|
|
902
917
|
from sqlmodel import Relationship
|
|
903
|
-
from sqlmodel_ext import UUIDTableBaseMixin, SQLModelBase
|
|
918
|
+
from sqlmodel_ext import UUIDTableBaseMixin, SQLModelBase, NonNegativeDecimal38_18
|
|
904
919
|
from sqlmodel_ext.mixins import RelationPreloadMixin, requires_relations
|
|
905
920
|
|
|
906
921
|
class GeneratorConfig(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
907
|
-
price:
|
|
922
|
+
price: NonNegativeDecimal38_18
|
|
908
923
|
|
|
909
924
|
class Generator(SQLModelBase, UUIDTableBaseMixin, table=True):
|
|
910
925
|
config: GeneratorConfig = Relationship()
|
|
@@ -1346,12 +1361,12 @@ When using Pydantic's `use_attribute_docstrings=True` (enabled by default in `SQ
|
|
|
1346
1361
|
|
|
1347
1362
|
```python
|
|
1348
1363
|
class UserBase(SQLModelBase):
|
|
1349
|
-
name:
|
|
1364
|
+
name: NonEmptyStrippedStr64
|
|
1350
1365
|
"""User display name""" # ← parsed by Pydantic
|
|
1351
1366
|
|
|
1352
1367
|
class UserUpdateRequest(UserBase, all_fields_optional=True):
|
|
1353
1368
|
pass
|
|
1354
|
-
# name:
|
|
1369
|
+
# name: NonEmptyStrippedStr64 | None = None — description "User display name" is inherited
|
|
1355
1370
|
# Shows up correctly in OpenAPI/Swagger docs
|
|
1356
1371
|
```
|
|
1357
1372
|
|