tina4-python 3.13.69__tar.gz → 3.13.70__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.69 → tina4_python-3.13.70}/PKG-INFO +1 -1
- {tina4_python-3.13.69 → tina4_python-3.13.70}/pyproject.toml +1 -1
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/firebird.py +35 -2
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/dev_admin/__init__.py +7 -49
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/dev_admin/metrics.py +1 -1
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/mcp/tools.py +27 -14
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/orm/model.py +51 -2
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/seeder/__init__.py +68 -1
- {tina4_python-3.13.69 → tina4_python-3.13.70}/.gitignore +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/README.md +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/env.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -8,7 +8,7 @@ Requires: pip install firebird-driver (or pip install fdb for legacy)
|
|
|
8
8
|
"""
|
|
9
9
|
import os
|
|
10
10
|
import re
|
|
11
|
-
from urllib.parse import urlparse, unquote
|
|
11
|
+
from urllib.parse import urlparse, unquote, parse_qs
|
|
12
12
|
from tina4_python.database.adapter import DatabaseAdapter, DatabaseResult, SQLTranslator
|
|
13
13
|
|
|
14
14
|
|
|
@@ -68,6 +68,35 @@ def _normalize_firebird_db_identifier(raw_path: str) -> str:
|
|
|
68
68
|
return decoded if decoded.startswith("/") else "/" + decoded
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
def _resolve_firebird_charset(connection_string: str, kwarg_charset: str | None = None) -> str:
|
|
72
|
+
"""Resolve the Firebird connection charset (php #160).
|
|
73
|
+
|
|
74
|
+
The adapter used to hardcode ``UTF8`` with no override, which
|
|
75
|
+
double-encodes UTF-8 bytes stored under a legacy ``NONE`` database. This
|
|
76
|
+
resolves the charset from, in precedence order:
|
|
77
|
+
|
|
78
|
+
1. the connection URL query — ``firebird://host:port/path?charset=NONE``
|
|
79
|
+
2. an explicit ``charset=`` keyword passed to :meth:`connect`
|
|
80
|
+
3. the ``TINA4_DATABASE_CHARSET`` environment variable
|
|
81
|
+
4. the ``UTF8`` default (unchanged — non-breaking for existing connections)
|
|
82
|
+
|
|
83
|
+
Pure config resolution over its inputs (URL string, kwarg, env) — no
|
|
84
|
+
connection is opened, so it is unit-testable without a live server.
|
|
85
|
+
"""
|
|
86
|
+
url_charset = None
|
|
87
|
+
query = urlparse(connection_string).query
|
|
88
|
+
if query:
|
|
89
|
+
values = parse_qs(query).get("charset")
|
|
90
|
+
if values:
|
|
91
|
+
url_charset = values[0]
|
|
92
|
+
return (
|
|
93
|
+
url_charset
|
|
94
|
+
or kwarg_charset
|
|
95
|
+
or os.environ.get("TINA4_DATABASE_CHARSET")
|
|
96
|
+
or "UTF8"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
71
100
|
# Try modern firebird-driver first, fall back to legacy fdb
|
|
72
101
|
_driver = None
|
|
73
102
|
_driver_name = None
|
|
@@ -142,7 +171,11 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
142
171
|
|
|
143
172
|
user = parsed.username or username or "SYSDBA"
|
|
144
173
|
password = parsed.password or password or "masterkey"
|
|
145
|
-
|
|
174
|
+
# php #160: honour ?charset= in the URL and TINA4_DATABASE_CHARSET so a
|
|
175
|
+
# legacy NONE database isn't force-connected as UTF8 (double-encoding).
|
|
176
|
+
# Pop the kwarg first so it isn't also forwarded into the driver connect
|
|
177
|
+
# via **extra below (which would raise a duplicate-keyword error).
|
|
178
|
+
charset = _resolve_firebird_charset(connection_string, kwargs.pop("charset", None))
|
|
146
179
|
|
|
147
180
|
# Cache for transparent reconnect — never logged, lives only in
|
|
148
181
|
# adapter memory alongside the connection it owns.
|
|
@@ -953,7 +953,7 @@ async def _api_seed_table(request, response):
|
|
|
953
953
|
"""Seed fake data into a database table from the admin UI."""
|
|
954
954
|
try:
|
|
955
955
|
from tina4_python.database import Database
|
|
956
|
-
from tina4_python.seeder import FakeData, seed_table
|
|
956
|
+
from tina4_python.seeder import FakeData, seed_table, auto_field_map
|
|
957
957
|
|
|
958
958
|
body = request.body if hasattr(request, "body") and request.body else {}
|
|
959
959
|
table = body.get("table", "")
|
|
@@ -985,54 +985,12 @@ async def _api_seed_table(request, response):
|
|
|
985
985
|
|
|
986
986
|
fake = FakeData(seed=seed_val)
|
|
987
987
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
return fake.email
|
|
995
|
-
if "name" in name_lower and "user" in name_lower:
|
|
996
|
-
return fake.name
|
|
997
|
-
if "first" in name_lower and "name" in name_lower:
|
|
998
|
-
return fake.first_name
|
|
999
|
-
if "last" in name_lower and "name" in name_lower:
|
|
1000
|
-
return fake.last_name
|
|
1001
|
-
if "name" in name_lower:
|
|
1002
|
-
return fake.name
|
|
1003
|
-
if "phone" in name_lower or "tel" in name_lower:
|
|
1004
|
-
return fake.phone
|
|
1005
|
-
if "url" in name_lower or "link" in name_lower:
|
|
1006
|
-
return fake.url
|
|
1007
|
-
if "address" in name_lower:
|
|
1008
|
-
return fake.address
|
|
1009
|
-
if "date" in name_lower or "time" in name_lower or "created" in name_lower:
|
|
1010
|
-
return fake.datetime_iso
|
|
1011
|
-
if "desc" in name_lower or "body" in name_lower or "content" in name_lower:
|
|
1012
|
-
return fake.paragraph
|
|
1013
|
-
if "title" in name_lower or "subject" in name_lower:
|
|
1014
|
-
return fake.sentence
|
|
1015
|
-
if "active" in name_lower or "enabled" in name_lower or "done" in name_lower:
|
|
1016
|
-
return fake.boolean
|
|
1017
|
-
if "INT" in col_type or "SERIAL" in col_type:
|
|
1018
|
-
return lambda: fake.integer(1, 10000)
|
|
1019
|
-
if any(t in col_type for t in ("REAL", "FLOAT", "DOUBLE", "NUMERIC", "DECIMAL")):
|
|
1020
|
-
return lambda: fake.decimal(0, 1000)
|
|
1021
|
-
if "BOOL" in col_type:
|
|
1022
|
-
return fake.boolean
|
|
1023
|
-
return fake.sentence
|
|
1024
|
-
|
|
1025
|
-
# Build a field_map skipping auto-increment / id PKs, then delegate to
|
|
1026
|
-
# seed_table so this endpoint shares the exact same visible-but-resilient
|
|
1027
|
-
# per-row error handling (P1/P4b) — no unhandled row failure crashes it.
|
|
1028
|
-
field_map = {}
|
|
1029
|
-
for col in columns:
|
|
1030
|
-
name = col.get("name", col.get("column_name", ""))
|
|
1031
|
-
col_type = col.get("type", col.get("data_type", "")).upper()
|
|
1032
|
-
is_pk = col.get("primary_key", col.get("pk", False))
|
|
1033
|
-
if is_pk and ("AUTO" in col_type or "SERIAL" in col_type or name.lower() == "id"):
|
|
1034
|
-
continue
|
|
1035
|
-
field_map[name] = _gen_for_column(col)
|
|
988
|
+
# Build a field_map (column -> generator, skipping auto-increment / id
|
|
989
|
+
# PKs) via the shared seeder helper, then delegate to seed_table so this
|
|
990
|
+
# endpoint shares the exact same visible-but-resilient per-row error
|
|
991
|
+
# handling (P1/P4b) — no unhandled row failure crashes it. The MCP
|
|
992
|
+
# seed_table dev tool uses the identical helper for parity.
|
|
993
|
+
field_map = auto_field_map(db, table, fake)
|
|
1036
994
|
|
|
1037
995
|
try:
|
|
1038
996
|
summary = seed_table(db, table, count, field_map=field_map,
|
|
@@ -129,7 +129,7 @@ def quick_metrics(root: str = "src") -> dict:
|
|
|
129
129
|
orm_count = 0
|
|
130
130
|
try:
|
|
131
131
|
from tina4_python.core.router import Router
|
|
132
|
-
route_count = len(Router.
|
|
132
|
+
route_count = len(Router.get_routes())
|
|
133
133
|
except Exception:
|
|
134
134
|
pass
|
|
135
135
|
try:
|
|
@@ -307,7 +307,11 @@ def register_dev_tools(server):
|
|
|
307
307
|
def swagger_spec() -> dict:
|
|
308
308
|
"""Return the full OpenAPI 3.0.3 JSON specification."""
|
|
309
309
|
from tina4_python.swagger import Swagger
|
|
310
|
-
|
|
310
|
+
from tina4_python.core.router import Router
|
|
311
|
+
# Swagger.generate is an INSTANCE method that takes the route table;
|
|
312
|
+
# build the spec from the live routes (same shape AutoCrud/the dev
|
|
313
|
+
# /swagger endpoint use). Canonical call: Swagger().generate(Router.get_routes()).
|
|
314
|
+
return Swagger().generate(Router.get_routes())
|
|
311
315
|
|
|
312
316
|
# ── Template Tools ──────────────────────────────────────────
|
|
313
317
|
|
|
@@ -481,15 +485,15 @@ def register_dev_tools(server):
|
|
|
481
485
|
|
|
482
486
|
# ── Migration Tools ─────────────────────────────────────────
|
|
483
487
|
|
|
484
|
-
def migration_status() ->
|
|
488
|
+
def migration_status() -> dict:
|
|
485
489
|
"""List pending and completed migrations."""
|
|
486
490
|
from tina4_python.orm.model import ORM
|
|
487
|
-
from tina4_python.migration
|
|
488
|
-
db = ORM.
|
|
491
|
+
from tina4_python.migration import status
|
|
492
|
+
db = ORM._get_db() if hasattr(ORM, "_get_db") else None
|
|
489
493
|
if db is None:
|
|
490
494
|
return {"error": "No database connection"}
|
|
491
|
-
|
|
492
|
-
return
|
|
495
|
+
# status(db) == Migration(db).status(); returns {"completed": [...], "pending": [...]}.
|
|
496
|
+
return status(db)
|
|
493
497
|
|
|
494
498
|
def migration_create(description: str) -> dict:
|
|
495
499
|
"""Create a new migration file. Refuses to create a duplicate
|
|
@@ -541,12 +545,14 @@ def register_dev_tools(server):
|
|
|
541
545
|
def migration_run() -> dict:
|
|
542
546
|
"""Run all pending migrations."""
|
|
543
547
|
from tina4_python.orm.model import ORM
|
|
544
|
-
from tina4_python.migration import
|
|
545
|
-
db = ORM.
|
|
548
|
+
from tina4_python.migration import migrate
|
|
549
|
+
db = ORM._get_db() if hasattr(ORM, "_get_db") else None
|
|
546
550
|
if db is None:
|
|
547
551
|
return {"error": "No database connection"}
|
|
548
|
-
|
|
549
|
-
|
|
552
|
+
# migrate(db) == Migration(db).migrate(); returns the list of applied
|
|
553
|
+
# migration filenames (empty when nothing was pending).
|
|
554
|
+
applied = migrate(db)
|
|
555
|
+
return {"applied": applied, "count": len(applied)}
|
|
550
556
|
|
|
551
557
|
# ── Queue Tools ─────────────────────────────────────────────
|
|
552
558
|
|
|
@@ -638,13 +644,20 @@ def register_dev_tools(server):
|
|
|
638
644
|
def seed_table(table: str, count: int = 10) -> dict:
|
|
639
645
|
"""Seed a table with fake data."""
|
|
640
646
|
try:
|
|
641
|
-
from tina4_python.seeder import seed_table as _seed
|
|
647
|
+
from tina4_python.seeder import seed_table as _seed, auto_field_map
|
|
642
648
|
from tina4_python.orm.model import ORM
|
|
643
|
-
db = ORM.
|
|
649
|
+
db = ORM._get_db() if hasattr(ORM, "_get_db") else None
|
|
644
650
|
if db is None:
|
|
645
651
|
return {"error": "No database connection"}
|
|
646
|
-
|
|
647
|
-
|
|
652
|
+
# seed_table needs an explicit column->generator field_map (no map =
|
|
653
|
+
# no rows, by design). auto_field_map introspects the table columns
|
|
654
|
+
# and builds one — the same helper the dev-admin seeding endpoint
|
|
655
|
+
# uses — so the tool actually inserts fake rows.
|
|
656
|
+
field_map = auto_field_map(db, table)
|
|
657
|
+
if not field_map:
|
|
658
|
+
return {"error": f"Table '{table}' not found or has no seedable columns"}
|
|
659
|
+
summary = _seed(db, table, count, field_map=field_map)
|
|
660
|
+
return {"table": table, "inserted": summary.seeded, "failed": summary.failed}
|
|
648
661
|
except Exception as e:
|
|
649
662
|
return {"error": str(e)}
|
|
650
663
|
|
|
@@ -183,6 +183,15 @@ class ORM(metaclass=ORMMeta):
|
|
|
183
183
|
# Initialize relationship cache
|
|
184
184
|
self._rel_cache = {}
|
|
185
185
|
|
|
186
|
+
# #165: track which fields the caller EXPLICITLY assigned (via
|
|
187
|
+
# constructor data/kwargs, _populate, or ``model.field = x``). save()
|
|
188
|
+
# uses this to OMIT an unset column from an INSERT — so a NOT NULL
|
|
189
|
+
# DEFAULT column gets its DB default instead of an explicit NULL —
|
|
190
|
+
# while still writing NULL for a field the caller set to None. The
|
|
191
|
+
# defaults seeded below go through object.__setattr__ so they are NOT
|
|
192
|
+
# counted as caller assignments.
|
|
193
|
+
self._assigned_fields = set()
|
|
194
|
+
|
|
186
195
|
# Set defaults from field definitions.
|
|
187
196
|
# v3.13.11 (issue #50): callable defaults are evaluated *per
|
|
188
197
|
# instance* so per-row timestamps (e.g. ``default=lambda:
|
|
@@ -190,7 +199,7 @@ class ORM(metaclass=ORMMeta):
|
|
|
190
199
|
# object reached the driver verbatim and blew up with
|
|
191
200
|
# ``can't adapt type 'function'``.
|
|
192
201
|
for name, field in self._fields.items():
|
|
193
|
-
|
|
202
|
+
object.__setattr__(self, name, field._resolve_default())
|
|
194
203
|
|
|
195
204
|
# Accept JSON string or dict
|
|
196
205
|
if isinstance(data, str):
|
|
@@ -213,6 +222,22 @@ class ORM(metaclass=ORMMeta):
|
|
|
213
222
|
if kwargs:
|
|
214
223
|
self._populate(kwargs)
|
|
215
224
|
|
|
225
|
+
def __setattr__(self, name, value):
|
|
226
|
+
"""Set an attribute, recording explicit assignment to declared fields.
|
|
227
|
+
|
|
228
|
+
#165: ``save()`` reads ``_assigned_fields`` to tell a column the caller
|
|
229
|
+
actually assigned (write it — an explicit ``None`` becomes ``NULL``)
|
|
230
|
+
from one left at its default (OMIT from the INSERT so a
|
|
231
|
+
``NOT NULL DEFAULT`` column gets its DB default rather than an explicit
|
|
232
|
+
``NULL``). ``__init__`` seeds field defaults with ``object.__setattr__``,
|
|
233
|
+
so only genuine caller assignments are tracked here. Non-field attrs
|
|
234
|
+
(``last_error``, ``_rel_cache``, relationships, …) are never recorded.
|
|
235
|
+
"""
|
|
236
|
+
assigned = self.__dict__.get("_assigned_fields")
|
|
237
|
+
if assigned is not None and name in type(self)._fields:
|
|
238
|
+
assigned.add(name)
|
|
239
|
+
object.__setattr__(self, name, value)
|
|
240
|
+
|
|
216
241
|
def _populate(self, data: dict):
|
|
217
242
|
"""Set field values from a dict.
|
|
218
243
|
|
|
@@ -377,6 +402,11 @@ class ORM(metaclass=ORMMeta):
|
|
|
377
402
|
pk_db_col = self.field_mapping.get(pk, self._fields[pk].column)
|
|
378
403
|
|
|
379
404
|
data = {}
|
|
405
|
+
# #165: db columns to OMIT from an INSERT — those the caller never
|
|
406
|
+
# assigned AND whose value is None. Omitting them lets the DB DEFAULT
|
|
407
|
+
# apply (e.g. NOT NULL DEFAULT '') instead of emitting an explicit NULL
|
|
408
|
+
# that violates the constraint. Unused on the UPDATE path.
|
|
409
|
+
insert_omit = set()
|
|
380
410
|
for name, field in self._fields.items():
|
|
381
411
|
if field.auto_increment and pk_value is None:
|
|
382
412
|
continue # Skip auto-increment on insert
|
|
@@ -393,6 +423,13 @@ class ORM(metaclass=ORMMeta):
|
|
|
393
423
|
# Serialize to the column's storage form: identity for most
|
|
394
424
|
# fields, JSON string for JSONField (see Field.to_db).
|
|
395
425
|
data[db_col] = field.to_db(value)
|
|
426
|
+
# #165: a None value the caller never assigned is an UNSET
|
|
427
|
+
# column — omit it from the INSERT so the DB DEFAULT applies.
|
|
428
|
+
# A resolved ORM default (non-None) is still written; a value
|
|
429
|
+
# the caller explicitly set to None is still written as NULL
|
|
430
|
+
# (its field name is in _assigned_fields).
|
|
431
|
+
if value is None and name not in self._assigned_fields:
|
|
432
|
+
insert_omit.add(db_col)
|
|
396
433
|
|
|
397
434
|
# v3.13.11 (issue #50): pick INSERT vs UPDATE on row existence
|
|
398
435
|
# for non-auto-increment PKs. Auto-increment keeps the legacy
|
|
@@ -420,7 +457,19 @@ class ORM(metaclass=ORMMeta):
|
|
|
420
457
|
if update_data:
|
|
421
458
|
db.update(table, update_data, f"{pk_db_col} = ?", [pk_value])
|
|
422
459
|
else:
|
|
423
|
-
|
|
460
|
+
# #165: drop unset-None columns so the DB DEFAULT applies.
|
|
461
|
+
insert_data = {c: v for c, v in data.items() if c not in insert_omit}
|
|
462
|
+
if insert_data:
|
|
463
|
+
db.insert(table, insert_data)
|
|
464
|
+
else:
|
|
465
|
+
# Every insertable column was left unset — let the DB
|
|
466
|
+
# apply ALL its column defaults rather than emitting
|
|
467
|
+
# explicit NULLs. DEFAULT VALUES is valid on SQLite /
|
|
468
|
+
# PostgreSQL / MSSQL / Firebird; MySQL spells it () VALUES ().
|
|
469
|
+
if db.get_database_type() == "mysql":
|
|
470
|
+
db.execute(f"INSERT INTO {table} () VALUES ()")
|
|
471
|
+
else:
|
|
472
|
+
db.execute(f"INSERT INTO {table} DEFAULT VALUES")
|
|
424
473
|
# Only adopt the engine-assigned ID for auto-increment PKs.
|
|
425
474
|
# Natural-key PKs were already set by the caller; don't
|
|
426
475
|
# overwrite them with the driver's last_id (which on PG
|
|
@@ -346,6 +346,72 @@ class SeedSummary(int):
|
|
|
346
346
|
return f"SeedSummary(seeded={self.seeded}, failed={self.failed}, errors={self.errors!r})"
|
|
347
347
|
|
|
348
348
|
|
|
349
|
+
def _generator_for_column(fake: "FakeData", name: str, col_type: str):
|
|
350
|
+
"""Pick a FakeData generator for one column from its name + type.
|
|
351
|
+
|
|
352
|
+
The shared heuristic behind :func:`auto_field_map`, so the dev-admin
|
|
353
|
+
seeding endpoint and the MCP ``seed_table`` dev tool auto-generate
|
|
354
|
+
identical column data. Falls back to a sentence for anything unrecognised.
|
|
355
|
+
"""
|
|
356
|
+
name_lower = (name or "").lower()
|
|
357
|
+
col_type = (col_type or "").upper()
|
|
358
|
+
if "email" in name_lower:
|
|
359
|
+
return fake.email
|
|
360
|
+
if "name" in name_lower and "user" in name_lower:
|
|
361
|
+
return fake.name
|
|
362
|
+
if "first" in name_lower and "name" in name_lower:
|
|
363
|
+
return fake.first_name
|
|
364
|
+
if "last" in name_lower and "name" in name_lower:
|
|
365
|
+
return fake.last_name
|
|
366
|
+
if "name" in name_lower:
|
|
367
|
+
return fake.name
|
|
368
|
+
if "phone" in name_lower or "tel" in name_lower:
|
|
369
|
+
return fake.phone
|
|
370
|
+
if "url" in name_lower or "link" in name_lower:
|
|
371
|
+
return fake.url
|
|
372
|
+
if "address" in name_lower:
|
|
373
|
+
return fake.address
|
|
374
|
+
if "date" in name_lower or "time" in name_lower or "created" in name_lower:
|
|
375
|
+
return fake.datetime_iso
|
|
376
|
+
if "desc" in name_lower or "body" in name_lower or "content" in name_lower:
|
|
377
|
+
return fake.paragraph
|
|
378
|
+
if "title" in name_lower or "subject" in name_lower:
|
|
379
|
+
return fake.sentence
|
|
380
|
+
if "active" in name_lower or "enabled" in name_lower or "done" in name_lower:
|
|
381
|
+
return fake.boolean
|
|
382
|
+
if "INT" in col_type or "SERIAL" in col_type:
|
|
383
|
+
return lambda: fake.integer(1, 10000)
|
|
384
|
+
if any(t in col_type for t in ("REAL", "FLOAT", "DOUBLE", "NUMERIC", "DECIMAL")):
|
|
385
|
+
return lambda: fake.decimal(0, 1000)
|
|
386
|
+
if "BOOL" in col_type:
|
|
387
|
+
return fake.boolean
|
|
388
|
+
return fake.sentence
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def auto_field_map(db, table: str, fake: "FakeData" = None) -> dict:
|
|
392
|
+
"""Introspect ``table``'s columns and build a column->generator field_map
|
|
393
|
+
for :func:`seed_table`, skipping auto-increment / ``id`` primary keys.
|
|
394
|
+
|
|
395
|
+
This is the "seed a table I did not hand-write generators for" convenience
|
|
396
|
+
that both the dev-admin seeding endpoint and the MCP ``seed_table`` dev tool
|
|
397
|
+
need. ``seed_table`` itself stays explicit (no field_map = no rows); this
|
|
398
|
+
helper is how a caller opts into automatic generation. Returns an empty dict
|
|
399
|
+
when the table has no columns, so ``seed_table`` then seeds nothing rather
|
|
400
|
+
than crashing.
|
|
401
|
+
"""
|
|
402
|
+
fake = fake or FakeData()
|
|
403
|
+
columns = db.get_columns(table) or []
|
|
404
|
+
field_map: dict[str, callable] = {}
|
|
405
|
+
for col in columns:
|
|
406
|
+
name = col.get("name", col.get("column_name", ""))
|
|
407
|
+
col_type = str(col.get("type", col.get("data_type", "")) or "").upper()
|
|
408
|
+
is_pk = col.get("primary_key", col.get("pk", False))
|
|
409
|
+
if is_pk and ("AUTO" in col_type or "SERIAL" in col_type or name.lower() == "id"):
|
|
410
|
+
continue
|
|
411
|
+
field_map[name] = _generator_for_column(fake, name, col_type)
|
|
412
|
+
return field_map
|
|
413
|
+
|
|
414
|
+
|
|
349
415
|
def seed_table(db, table: str, count: int = 10,
|
|
350
416
|
field_map: dict[str, callable] = None,
|
|
351
417
|
overrides: dict = None, clear: bool = False,
|
|
@@ -766,4 +832,5 @@ def _generate_for_field(fake: FakeData, field_def: dict, col: str):
|
|
|
766
832
|
return fake.word()
|
|
767
833
|
|
|
768
834
|
|
|
769
|
-
__all__ = ["FakeData", "SeedSummary", "seed_table", "seed_orm", "seed_models"
|
|
835
|
+
__all__ = ["FakeData", "SeedSummary", "seed_table", "seed_orm", "seed_models",
|
|
836
|
+
"auto_field_map"]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/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.69 → tina4_python-3.13.70}/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.69 → tina4_python-3.13.70}/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
|
|
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.69 → tina4_python-3.13.70}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/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.69 → tina4_python-3.13.70}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.69 → tina4_python-3.13.70}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|