tina4-python 3.13.5__tar.gz → 3.13.7__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.5 → tina4_python-3.13.7}/PKG-INFO +1 -1
- {tina4_python-3.13.5 → tina4_python-3.13.7}/pyproject.toml +1 -1
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/server.py +33 -4
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/firebird.py +4 -2
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/mongodb.py +4 -2
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/mssql.py +4 -2
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/mysql.py +4 -2
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/odbc.py +4 -1
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/postgres.py +75 -6
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue_backends/mongo_backend.py +4 -2
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/500.twig +1 -1
- {tina4_python-3.13.5 → tina4_python-3.13.7}/.gitignore +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/README.md +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/env.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -1307,9 +1307,38 @@ def _handle_route_error(
|
|
|
1307
1307
|
error: Exception, request: Request, response: Response,
|
|
1308
1308
|
request_id: str, is_dev: bool,
|
|
1309
1309
|
) -> Response:
|
|
1310
|
-
"""Format an error response for a failed route handler.
|
|
1310
|
+
"""Format an error response for a failed route handler.
|
|
1311
|
+
|
|
1312
|
+
Two contracts (v3.13.7):
|
|
1313
|
+
|
|
1314
|
+
1. Fire ``tina4.request.error`` before rendering, so observers
|
|
1315
|
+
(centralised logging, APM, Sentry) see the failure even though
|
|
1316
|
+
the framework caught it. Payload is a single dict ``{exception,
|
|
1317
|
+
request}`` — the canonical shape mirrored by PHP/Ruby/Node.
|
|
1318
|
+
Listener exceptions are swallowed so a broken listener can't
|
|
1319
|
+
break the 500 page.
|
|
1320
|
+
|
|
1321
|
+
2. In non-debug mode, never put the stack trace in the response
|
|
1322
|
+
body (CWE-209). The trace is still available via ``Log.error``
|
|
1323
|
+
above and the dev-admin ``BrokenTracker`` in dev mode. The
|
|
1324
|
+
framework's own 500.twig now guards the trace block with
|
|
1325
|
+
``{% if error_message %}``.
|
|
1326
|
+
"""
|
|
1311
1327
|
Log.error(f"Route error: {error}", path=request.path)
|
|
1312
1328
|
_write_broken(request, error)
|
|
1329
|
+
|
|
1330
|
+
try:
|
|
1331
|
+
from tina4_python.core.events import emit as _emit
|
|
1332
|
+
_emit("tina4.request.error", {"exception": error, "request": request})
|
|
1333
|
+
except Exception as listener_err:
|
|
1334
|
+
try:
|
|
1335
|
+
Log.warning(
|
|
1336
|
+
f"Listener for tina4.request.error raised: "
|
|
1337
|
+
f"{type(listener_err).__name__}: {listener_err}"
|
|
1338
|
+
)
|
|
1339
|
+
except Exception:
|
|
1340
|
+
pass
|
|
1341
|
+
|
|
1313
1342
|
if is_dev:
|
|
1314
1343
|
try:
|
|
1315
1344
|
import traceback as _tb
|
|
@@ -1324,9 +1353,9 @@ def _handle_route_error(
|
|
|
1324
1353
|
overlay_html = render_error_overlay(error, request)
|
|
1325
1354
|
response.status(500).html(overlay_html)
|
|
1326
1355
|
else:
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
html = _render_error_page(500, request.path, request_id,
|
|
1356
|
+
# Production: NO traceback in the body. The trace is logged via
|
|
1357
|
+
# Log.error above; clients only see the generic page + request_id.
|
|
1358
|
+
html = _render_error_page(500, request.path, request_id, "")
|
|
1330
1359
|
if html:
|
|
1331
1360
|
response.status(500).html(html)
|
|
1332
1361
|
else:
|
|
@@ -116,8 +116,10 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
116
116
|
"""
|
|
117
117
|
if _driver is None:
|
|
118
118
|
raise ImportError(
|
|
119
|
-
"A Firebird driver is required. "
|
|
120
|
-
"
|
|
119
|
+
"A Firebird driver is required. Install one of:\n"
|
|
120
|
+
" uv add tina4-python[firebird] # extra for projects using uv\n"
|
|
121
|
+
" pip install firebird-driver # bare driver (or 'fdb' for legacy)\n"
|
|
122
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
121
123
|
)
|
|
122
124
|
|
|
123
125
|
parsed = urlparse(connection_string)
|
|
@@ -348,8 +348,10 @@ class MongoDBAdapter(DatabaseAdapter):
|
|
|
348
348
|
import pymongo
|
|
349
349
|
except ImportError:
|
|
350
350
|
raise ImportError(
|
|
351
|
-
"pymongo is required for MongoDB connections. "
|
|
352
|
-
"
|
|
351
|
+
"pymongo is required for MongoDB connections. Install one of:\n"
|
|
352
|
+
" uv add tina4-python[mongo] # extra for projects using uv\n"
|
|
353
|
+
" pip install pymongo # bare driver\n"
|
|
354
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
353
355
|
)
|
|
354
356
|
|
|
355
357
|
# Normalise the scheme so MongoClient accepts it
|
|
@@ -29,8 +29,10 @@ class MSSQLAdapter(DatabaseAdapter):
|
|
|
29
29
|
import pymssql
|
|
30
30
|
except ImportError:
|
|
31
31
|
raise ImportError(
|
|
32
|
-
"pymssql is required for MSSQL connections. "
|
|
33
|
-
"
|
|
32
|
+
"pymssql is required for MSSQL connections. Install one of:\n"
|
|
33
|
+
" uv add tina4-python[mssql] # extra for projects using uv\n"
|
|
34
|
+
" pip install pymssql # bare driver\n"
|
|
35
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
parsed = urlparse(connection_string)
|
|
@@ -29,8 +29,10 @@ class MySQLAdapter(DatabaseAdapter):
|
|
|
29
29
|
import mysql.connector
|
|
30
30
|
except ImportError:
|
|
31
31
|
raise ImportError(
|
|
32
|
-
"mysql-connector-python is required for MySQL connections. "
|
|
33
|
-
"
|
|
32
|
+
"mysql-connector-python is required for MySQL connections. Install one of:\n"
|
|
33
|
+
" uv add tina4-python[mysql] # extra for projects using uv\n"
|
|
34
|
+
" pip install mysql-connector-python # bare driver\n"
|
|
35
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
34
36
|
)
|
|
35
37
|
|
|
36
38
|
parsed = urlparse(connection_string)
|
|
@@ -31,7 +31,10 @@ class ODBCAdapter(DatabaseAdapter):
|
|
|
31
31
|
import pyodbc
|
|
32
32
|
except ImportError:
|
|
33
33
|
raise ImportError(
|
|
34
|
-
"pyodbc is required for ODBC connections. Install
|
|
34
|
+
"pyodbc is required for ODBC connections. Install one of:\n"
|
|
35
|
+
" uv add tina4-python[odbc] # extra for projects using uv\n"
|
|
36
|
+
" pip install pyodbc # bare driver\n"
|
|
37
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
# Strip URL prefix if present
|
|
@@ -19,6 +19,7 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
|
19
19
|
super().__init__()
|
|
20
20
|
self._conn = None
|
|
21
21
|
self._in_transaction: bool = False
|
|
22
|
+
self.last_error: str | None = None
|
|
22
23
|
|
|
23
24
|
@staticmethod
|
|
24
25
|
def _safe_execute(cursor, sql: str, params=None):
|
|
@@ -36,12 +37,76 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
|
36
37
|
Pass ``None`` (or omit the second arg) and psycopg2 skips the
|
|
37
38
|
substitution pass entirely — literal ``%`` flows through
|
|
38
39
|
untouched. So we route empty/None params through that path.
|
|
40
|
+
|
|
41
|
+
Kept static for direct unit-test access (a number of
|
|
42
|
+
regression tests in test_postgres_percent_substitution.py call
|
|
43
|
+
it with a fake cursor). For the error-aware variant that adds
|
|
44
|
+
logging + auto-rollback (issue #46), use the instance method
|
|
45
|
+
:meth:`_exec_with_handling`.
|
|
39
46
|
"""
|
|
40
47
|
if params:
|
|
41
48
|
cursor.execute(sql, params)
|
|
42
49
|
else:
|
|
43
50
|
cursor.execute(sql)
|
|
44
51
|
|
|
52
|
+
def _exec_with_handling(self, cursor, sql: str, params=None):
|
|
53
|
+
"""Run a query through :meth:`_safe_execute` and surface failures.
|
|
54
|
+
|
|
55
|
+
Issue #46: psycopg2 keeps the connection in an
|
|
56
|
+
``InFailedSqlTransaction`` state after any failed query — every
|
|
57
|
+
subsequent statement returns the cascade message ``current
|
|
58
|
+
transaction is aborted, commands ignored until end of transaction
|
|
59
|
+
block``, *far* from the actual cause. Without framework-side
|
|
60
|
+
logging, the original SQL + error never reach the user.
|
|
61
|
+
|
|
62
|
+
On failure this method:
|
|
63
|
+
|
|
64
|
+
1. Logs the original error via ``Log.error`` with SQL+params context
|
|
65
|
+
2. Stores it on ``self.last_error``
|
|
66
|
+
3. Auto-rollbacks when NOT inside an explicit transaction (so the
|
|
67
|
+
next caller gets a clean connection)
|
|
68
|
+
4. Re-raises so callers still see the failure
|
|
69
|
+
|
|
70
|
+
Inside an explicit transaction we do NOT rollback — the user owns
|
|
71
|
+
that transaction (could be running a SAVEPOINT/retry pattern). We
|
|
72
|
+
still log + store ``last_error`` so they can diagnose.
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
self._safe_execute(cursor, sql, params)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
self._on_query_error(e, sql, params)
|
|
78
|
+
raise
|
|
79
|
+
|
|
80
|
+
def _on_query_error(self, exc: Exception, sql: str, params=None):
|
|
81
|
+
"""Handle a failed query: log, store, auto-rollback if implicit."""
|
|
82
|
+
# Lazy import — avoid circular import at module load and let the
|
|
83
|
+
# framework boot without Log if something exotic is happening.
|
|
84
|
+
try:
|
|
85
|
+
from tina4_python.debug import Log
|
|
86
|
+
preview = sql.strip().splitlines()[0][:200] if sql else "<no sql>"
|
|
87
|
+
Log.error(
|
|
88
|
+
f"PostgreSQL query failed: {exc.__class__.__name__}: {exc}",
|
|
89
|
+
sql=preview,
|
|
90
|
+
params=params,
|
|
91
|
+
)
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
self.last_error = str(exc)
|
|
96
|
+
|
|
97
|
+
# If the query failed *outside* an explicit transaction block,
|
|
98
|
+
# rollback the implicit one so the connection becomes usable
|
|
99
|
+
# again. Without this, every subsequent query on this connection
|
|
100
|
+
# cascades into ``InFailedSqlTransaction`` until something else
|
|
101
|
+
# calls rollback. With it, the next caller sees a clean conn
|
|
102
|
+
# and the actual error from this call propagates as the cause.
|
|
103
|
+
if not self._in_transaction and self._conn is not None:
|
|
104
|
+
try:
|
|
105
|
+
self._conn.rollback()
|
|
106
|
+
except Exception:
|
|
107
|
+
# Don't mask the original error if rollback itself fails.
|
|
108
|
+
pass
|
|
109
|
+
|
|
45
110
|
def connect(self, connection_string: str, username: str = "", password: str = "", **kwargs):
|
|
46
111
|
"""Connect to PostgreSQL.
|
|
47
112
|
|
|
@@ -53,8 +118,10 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
|
53
118
|
import psycopg2.extras
|
|
54
119
|
except ImportError:
|
|
55
120
|
raise ImportError(
|
|
56
|
-
"psycopg2 is required for PostgreSQL connections. "
|
|
57
|
-
"
|
|
121
|
+
"psycopg2 is required for PostgreSQL connections. Install one of:\n"
|
|
122
|
+
" uv add tina4-python[postgres] # extra for projects using uv\n"
|
|
123
|
+
" pip install psycopg2-binary # bare driver\n"
|
|
124
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
58
125
|
)
|
|
59
126
|
|
|
60
127
|
parsed = urlparse(connection_string)
|
|
@@ -85,7 +152,7 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
|
85
152
|
)
|
|
86
153
|
|
|
87
154
|
cursor = self._conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
|
88
|
-
self.
|
|
155
|
+
self._exec_with_handling(cursor, sql, params)
|
|
89
156
|
|
|
90
157
|
records = []
|
|
91
158
|
last_id = None
|
|
@@ -151,10 +218,12 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
|
151
218
|
except Exception:
|
|
152
219
|
total = 0
|
|
153
220
|
|
|
154
|
-
# Apply pagination
|
|
221
|
+
# Apply pagination — the real query, so use the error-handling
|
|
222
|
+
# wrapper. The count probe above is best-effort and stays on
|
|
223
|
+
# _safe_execute (a failed probe shouldn't taint last_error).
|
|
155
224
|
paginated_sql = f"{sql} LIMIT %s OFFSET %s"
|
|
156
225
|
paginated_params = (params or []) + [limit, offset]
|
|
157
|
-
self.
|
|
226
|
+
self._exec_with_handling(cursor, paginated_sql, paginated_params)
|
|
158
227
|
rows = [self._decode_blobs(dict(row)) for row in cursor.fetchall()]
|
|
159
228
|
|
|
160
229
|
return DatabaseResult(records=rows, count=total, limit=limit, offset=offset, sql=sql, adapter=self)
|
|
@@ -164,7 +233,7 @@ class PostgreSQLAdapter(DatabaseAdapter):
|
|
|
164
233
|
|
|
165
234
|
sql = self._translate_sql(sql)
|
|
166
235
|
cursor = self._conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
|
|
167
|
-
self.
|
|
236
|
+
self._exec_with_handling(cursor, sql, params)
|
|
168
237
|
row = cursor.fetchone()
|
|
169
238
|
return self._decode_blobs(dict(row)) if row else None
|
|
170
239
|
|
|
@@ -59,8 +59,10 @@ class MongoConnector:
|
|
|
59
59
|
self._pymongo = pymongo
|
|
60
60
|
except ImportError:
|
|
61
61
|
raise ImportError(
|
|
62
|
-
"pymongo is required for the MongoDB queue backend. "
|
|
63
|
-
"
|
|
62
|
+
"pymongo is required for the MongoDB queue backend. Install one of:\n"
|
|
63
|
+
" uv add tina4-python[mongo] # extra for projects using uv\n"
|
|
64
|
+
" pip install pymongo # bare driver\n"
|
|
65
|
+
" uv add tina4-python[all-db] # all five database drivers"
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
# ── Public Interface ─────────────────────────────────────────
|
|
@@ -27,7 +27,7 @@ body { font-family: system-ui, -apple-system, sans-serif; background: #0f172a; c
|
|
|
27
27
|
<div class="error-title">Server Error</div>
|
|
28
28
|
</div>
|
|
29
29
|
<div class="error-msg">Something went wrong while processing your request.</div>
|
|
30
|
-
<pre class="error-trace">{{ error_message }}</pre>
|
|
30
|
+
{% if error_message %}<pre class="error-trace">{{ error_message }}</pre>{% endif %}
|
|
31
31
|
<div class="error-footer">
|
|
32
32
|
<span class="error-hint">Fix the error and save to auto-reload</span>
|
|
33
33
|
<span class="error-id">{{ request_id }}</span>
|
|
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.5 → tina4_python-3.13.7}/tina4_python/gallery/auth/src/routes/api/gallery_auth.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
|
|
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.5 → tina4_python-3.13.7}/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
|
|
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.5 → tina4_python-3.13.7}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/templates/docker/distroless/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
|
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.5 → tina4_python-3.13.7}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|