tina4-python 3.13.52__tar.gz → 3.13.53__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.
- {tina4_python-3.13.52 → tina4_python-3.13.53}/PKG-INFO +1 -1
- {tina4_python-3.13.52 → tina4_python-3.13.53}/pyproject.toml +1 -1
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/CLAUDE.md +3 -1
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/graphql/__init__.py +2 -1
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/orm/__init__.py +2 -2
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/orm/fields.py +86 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/orm/model.py +23 -2
- {tina4_python-3.13.52 → tina4_python-3.13.53}/.gitignore +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/README.md +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/env.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -901,7 +901,9 @@ User.query().where("name = ?", ["Alice"]).to_mongo()
|
|
|
901
901
|
```
|
|
902
902
|
|
|
903
903
|
### Available field types
|
|
904
|
-
`IntegerField`, `StringField`, `TextField`, `DateTimeField`, `NumericField`, `BlobField`, `BooleanField`, `FloatField`, `ForeignKeyField`
|
|
904
|
+
`IntegerField`, `StringField`, `TextField`, `DateTimeField`, `NumericField`, `BlobField`, `BooleanField`, `FloatField`, `JSONField`, `ForeignKeyField`
|
|
905
|
+
|
|
906
|
+
`JSONField()` stores a `dict`/`list` as JSON. You work with a plain Python object; the ORM serializes to a JSON string on save and parses it back on load, so `model.field` is always a `dict`/`list`. Engine-aware column: `JSONB` (PostgreSQL), `JSON` (MySQL), `NVARCHAR(MAX)` (MSSQL), `TEXT` (SQLite), `BLOB SUB_TYPE TEXT` (Firebird). Use it for a JSON column inside a relational row; use DocStore for whole-document storage with Mongo-style queries.
|
|
905
907
|
|
|
906
908
|
Import from `tina4_python` or `tina4_python.orm.fields`.
|
|
907
909
|
|
|
@@ -36,7 +36,7 @@ from tina4_python.database import Database # noqa: E402, F401
|
|
|
36
36
|
from tina4_python.orm import ( # noqa: E402, F401
|
|
37
37
|
ORM, bind_database, Field,
|
|
38
38
|
IntegerField, StringField, BooleanField, FloatField,
|
|
39
|
-
DateTimeField, TextField, BlobField, NumericField,
|
|
39
|
+
DateTimeField, TextField, BlobField, NumericField, JSONField,
|
|
40
40
|
ForeignKeyField,
|
|
41
41
|
IntField, StrField, BoolField, # short aliases
|
|
42
42
|
has_many, has_one, belongs_to, # relationship descriptors
|
|
@@ -382,7 +382,8 @@ class Schema:
|
|
|
382
382
|
gql_type = "Float"
|
|
383
383
|
elif ftype == "BooleanField":
|
|
384
384
|
gql_type = "Boolean"
|
|
385
|
-
elif ftype in ("StringField", "TextField", "DateTimeField"):
|
|
385
|
+
elif ftype in ("StringField", "TextField", "DateTimeField", "JSONField"):
|
|
386
|
+
# JSONField serializes to a JSON string in the GraphQL payload.
|
|
386
387
|
gql_type = "String"
|
|
387
388
|
else:
|
|
388
389
|
gql_type = "String"
|
|
@@ -19,7 +19,7 @@ Active Record ORM with SQL-first paradigm.
|
|
|
19
19
|
from tina4_python.orm.fields import (
|
|
20
20
|
Field,
|
|
21
21
|
IntegerField, StringField, BooleanField, FloatField,
|
|
22
|
-
DateTimeField, TextField, BlobField, NumericField,
|
|
22
|
+
DateTimeField, TextField, BlobField, NumericField, JSONField,
|
|
23
23
|
ForeignKeyField,
|
|
24
24
|
IntField, StrField, BoolField, # short aliases
|
|
25
25
|
has_many, has_one, belongs_to, # relationship descriptors
|
|
@@ -31,7 +31,7 @@ __all__ = [
|
|
|
31
31
|
"Field",
|
|
32
32
|
# Verbose (preferred)
|
|
33
33
|
"IntegerField", "StringField", "BooleanField", "FloatField",
|
|
34
|
-
"DateTimeField", "TextField", "BlobField", "NumericField",
|
|
34
|
+
"DateTimeField", "TextField", "BlobField", "NumericField", "JSONField",
|
|
35
35
|
"ForeignKeyField",
|
|
36
36
|
# Short aliases (backwards compat)
|
|
37
37
|
"IntField", "StrField", "BoolField",
|
|
@@ -9,6 +9,8 @@ Fields define column types, constraints, and defaults for ORM models.
|
|
|
9
9
|
role = Field(str, choices=["admin", "user", "guest"])
|
|
10
10
|
active = Field(bool, default=True)
|
|
11
11
|
"""
|
|
12
|
+
import copy as _copy
|
|
13
|
+
import json as _json
|
|
12
14
|
import re as _re
|
|
13
15
|
from datetime import datetime
|
|
14
16
|
|
|
@@ -165,6 +167,16 @@ class Field:
|
|
|
165
167
|
|
|
166
168
|
return value
|
|
167
169
|
|
|
170
|
+
def to_db(self, value):
|
|
171
|
+
"""Serialize an in-memory value to the form the driver should store.
|
|
172
|
+
|
|
173
|
+
Identity by default — the value already round-trips through the driver
|
|
174
|
+
as-is. Overridden by fields whose Python representation differs from
|
|
175
|
+
their column representation (e.g. ``JSONField`` stores a JSON string).
|
|
176
|
+
Called by ``ORM.save()`` when building the row for INSERT/UPDATE.
|
|
177
|
+
"""
|
|
178
|
+
return value
|
|
179
|
+
|
|
168
180
|
def __repr__(self):
|
|
169
181
|
parts = [self.field_type.__name__]
|
|
170
182
|
if self.primary_key:
|
|
@@ -212,6 +224,80 @@ def BlobField(**kwargs):
|
|
|
212
224
|
def NumericField(**kwargs):
|
|
213
225
|
return _make_field(float, "NumericField", **kwargs)
|
|
214
226
|
|
|
227
|
+
class JSONField(Field):
|
|
228
|
+
"""A column that stores a JSON document (a ``dict`` or a ``list``).
|
|
229
|
+
|
|
230
|
+
You work with a plain Python ``dict``/``list``; the ORM serializes it to a
|
|
231
|
+
JSON string on save and parses it back on load. The column DDL is
|
|
232
|
+
engine-aware (see :meth:`ORM.create_table`): ``JSONB`` on PostgreSQL,
|
|
233
|
+
``JSON`` on MySQL, ``NVARCHAR(MAX)`` on MSSQL, ``TEXT`` on SQLite/ODBC,
|
|
234
|
+
``BLOB SUB_TYPE TEXT`` on Firebird.
|
|
235
|
+
|
|
236
|
+
Read normalization: a native-JSON driver (PostgreSQL JSONB, MySQL JSON) may
|
|
237
|
+
hand back an already-parsed object, while a string-backed engine (SQLite)
|
|
238
|
+
hands back a JSON string. :meth:`validate` normalizes both to a Python
|
|
239
|
+
object, so ``model.data`` is always a ``dict``/``list`` regardless of engine.
|
|
240
|
+
|
|
241
|
+
Usage::
|
|
242
|
+
|
|
243
|
+
class Event(ORM):
|
|
244
|
+
id = IntegerField(primary_key=True, auto_increment=True)
|
|
245
|
+
payload = JSONField()
|
|
246
|
+
|
|
247
|
+
Event({"payload": {"type": "click", "tags": ["a", "b"]}}).save()
|
|
248
|
+
e = Event.find(1)
|
|
249
|
+
e.payload["type"] # "click" — a real dict, not a string
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(self, **kwargs):
|
|
253
|
+
super().__init__(dict, **kwargs)
|
|
254
|
+
self.kind = "JSONField"
|
|
255
|
+
|
|
256
|
+
def _resolve_default(self):
|
|
257
|
+
# Deep-copy a mutable JSON default (``default={}`` / ``default=[]``) so
|
|
258
|
+
# separate instances never alias the same object — the classic
|
|
259
|
+
# shared-default footgun. This runs on BOTH the __init__ default path
|
|
260
|
+
# (which calls _resolve_default directly) and the validate() None path.
|
|
261
|
+
d = super()._resolve_default()
|
|
262
|
+
return _copy.deepcopy(d) if isinstance(d, (dict, list)) else d
|
|
263
|
+
|
|
264
|
+
def validate(self, value):
|
|
265
|
+
# None -> required check + default (mirrors base Field).
|
|
266
|
+
if value is None:
|
|
267
|
+
if self.required and self.default is None:
|
|
268
|
+
raise ValueError(f"Field '{self.name}' is required")
|
|
269
|
+
return self._resolve_default()
|
|
270
|
+
|
|
271
|
+
# A driver returns a JSON string (SQLite/TEXT) or an already-parsed
|
|
272
|
+
# dict/list (PostgreSQL JSONB, MySQL JSON). Normalize to a Python object.
|
|
273
|
+
if isinstance(value, (bytes, bytearray)):
|
|
274
|
+
value = value.decode("utf-8")
|
|
275
|
+
if isinstance(value, str):
|
|
276
|
+
try:
|
|
277
|
+
value = _json.loads(value)
|
|
278
|
+
except (ValueError, TypeError) as e:
|
|
279
|
+
raise ValueError(f"Field '{self.name}': value is not valid JSON") from e
|
|
280
|
+
if not isinstance(value, (dict, list)):
|
|
281
|
+
raise ValueError(
|
|
282
|
+
f"Field '{self.name}': expected a dict or list, got {type(value).__name__}"
|
|
283
|
+
)
|
|
284
|
+
if self.validator is not None:
|
|
285
|
+
self.validator(value)
|
|
286
|
+
return value
|
|
287
|
+
|
|
288
|
+
def to_db(self, value):
|
|
289
|
+
# Serialize to a JSON string for the driver. A TEXT column stores it
|
|
290
|
+
# verbatim; a native JSON column (JSONB/JSON) casts the string literal.
|
|
291
|
+
if value is None:
|
|
292
|
+
return None
|
|
293
|
+
if isinstance(value, str):
|
|
294
|
+
return value # already serialized (defensive)
|
|
295
|
+
try:
|
|
296
|
+
return _json.dumps(value)
|
|
297
|
+
except (TypeError, ValueError) as e:
|
|
298
|
+
raise ValueError(f"Field '{self.name}': value is not JSON-serializable") from e
|
|
299
|
+
|
|
300
|
+
|
|
215
301
|
class ForeignKeyField(Field):
|
|
216
302
|
"""Integer field that references another model's primary key.
|
|
217
303
|
|
|
@@ -390,7 +390,9 @@ class ORM(metaclass=ORMMeta):
|
|
|
390
390
|
if value is not None or not field.auto_increment:
|
|
391
391
|
# Use field_mapping for the column name, fall back to field.column
|
|
392
392
|
db_col = self.field_mapping.get(name, field.column)
|
|
393
|
-
|
|
393
|
+
# Serialize to the column's storage form: identity for most
|
|
394
|
+
# fields, JSON string for JSONField (see Field.to_db).
|
|
395
|
+
data[db_col] = field.to_db(value)
|
|
394
396
|
|
|
395
397
|
# v3.13.11 (issue #50): pick INSERT vs UPDATE on row existence
|
|
396
398
|
# for non-auto-increment PKs. Auto-increment keeps the legacy
|
|
@@ -840,6 +842,23 @@ class ORM(metaclass=ORMMeta):
|
|
|
840
842
|
else:
|
|
841
843
|
datetime_sql = "DATETIME"
|
|
842
844
|
|
|
845
|
+
# JSONField -> the engine's native JSON type where it has one, else a
|
|
846
|
+
# text column. PostgreSQL JSONB (binary, indexable, canonical form);
|
|
847
|
+
# MySQL JSON; MSSQL has no JSON type so NVARCHAR(MAX) (its documented
|
|
848
|
+
# JSON storage); SQLite/ODBC TEXT; Firebird has no TEXT/JSON type so
|
|
849
|
+
# BLOB SUB_TYPE TEXT. The ORM stores a JSON string in every case, so a
|
|
850
|
+
# text-backed column round-trips identically to a native one.
|
|
851
|
+
if engine in ("postgres", "postgresql"):
|
|
852
|
+
json_sql = "JSONB"
|
|
853
|
+
elif engine == "mysql":
|
|
854
|
+
json_sql = "JSON"
|
|
855
|
+
elif engine == "mssql":
|
|
856
|
+
json_sql = "NVARCHAR(MAX)"
|
|
857
|
+
elif engine == "firebird":
|
|
858
|
+
json_sql = "BLOB SUB_TYPE TEXT"
|
|
859
|
+
else:
|
|
860
|
+
json_sql = "TEXT"
|
|
861
|
+
|
|
843
862
|
# Don't recreate if table already exists
|
|
844
863
|
if db.table_exists(table):
|
|
845
864
|
return True
|
|
@@ -866,6 +885,8 @@ class ORM(metaclass=ORMMeta):
|
|
|
866
885
|
sql_type = datetime_sql
|
|
867
886
|
elif kind == "BlobField":
|
|
868
887
|
sql_type = "BLOB"
|
|
888
|
+
elif kind == "JSONField":
|
|
889
|
+
sql_type = json_sql
|
|
869
890
|
else:
|
|
870
891
|
# Fallback based on field_type
|
|
871
892
|
ft = field_obj.field_type
|
|
@@ -886,7 +907,7 @@ class ORM(metaclass=ORMMeta):
|
|
|
886
907
|
parts.append("AUTOINCREMENT")
|
|
887
908
|
if field_obj.required and not field_obj.primary_key:
|
|
888
909
|
parts.append("NOT NULL")
|
|
889
|
-
if field_obj.default is not None and not field_obj.auto_increment:
|
|
910
|
+
if field_obj.default is not None and not field_obj.auto_increment and kind != "JSONField":
|
|
890
911
|
default_val = field_obj.default
|
|
891
912
|
if isinstance(default_val, str):
|
|
892
913
|
parts.append(f"DEFAULT '{default_val}'")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.52 → tina4_python-3.13.53}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|