tina4-python 3.12.0__tar.gz → 3.12.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-3.12.0 → tina4_python-3.12.2}/PKG-INFO +2 -2
- {tina4_python-3.12.0 → tina4_python-3.12.2}/pyproject.toml +2 -5
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/__init__.py +1 -1
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/firebird.py +163 -18
- {tina4_python-3.12.0 → tina4_python-3.12.2}/.gitignore +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/README.md +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/Testing.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/events.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/request.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/response.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/router.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/core/server.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/docs.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 3.12.
|
|
4
|
-
Summary: Tina4
|
|
3
|
+
Version: 3.12.2
|
|
4
|
+
Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
|
|
5
5
|
Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.12
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tina4-python"
|
|
3
|
-
|
|
4
|
-
description = "Tina4
|
|
3
|
+
version = "3.12.2"
|
|
4
|
+
description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
|
|
5
5
|
authors = [
|
|
6
6
|
{name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
|
|
7
7
|
]
|
|
@@ -55,9 +55,6 @@ dev = [
|
|
|
55
55
|
requires = ["hatchling"]
|
|
56
56
|
build-backend = "hatchling.build"
|
|
57
57
|
|
|
58
|
-
[tool.hatch.version]
|
|
59
|
-
path = "tina4_python/__init__.py"
|
|
60
|
-
|
|
61
58
|
[tool.hatch.build]
|
|
62
59
|
include = ["tina4_python/**/*"]
|
|
63
60
|
|
|
@@ -6,10 +6,68 @@ Firebird adapter using firebird-driver (preferred) or fdb (fallback).
|
|
|
6
6
|
|
|
7
7
|
Requires: pip install firebird-driver (or pip install fdb for legacy)
|
|
8
8
|
"""
|
|
9
|
+
import os
|
|
9
10
|
import re
|
|
10
11
|
from urllib.parse import urlparse, unquote
|
|
11
12
|
from tina4_python.database.adapter import DatabaseAdapter, DatabaseResult, SQLTranslator
|
|
12
13
|
|
|
14
|
+
|
|
15
|
+
# Detects a Windows drive-letter prefix like "C:/" or "C:\". The leading-slash
|
|
16
|
+
# variant ("/C:/...") shows up after a URL parse strips one slash off
|
|
17
|
+
# "firebird://host:port/C:/...".
|
|
18
|
+
_WIN_DRIVE_RE = re.compile(r"^/?[A-Za-z]:[/\\]")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _normalize_firebird_db_identifier(raw_path: str) -> str:
|
|
22
|
+
"""Turn the URL path component into a Firebird database identifier.
|
|
23
|
+
|
|
24
|
+
Firebird is the awkward one — it needs either an absolute file path
|
|
25
|
+
on the server, a Windows drive-letter path, or an alias name. The
|
|
26
|
+
classic URI form uses a double-slash to keep the leading "/" of an
|
|
27
|
+
absolute path through ``urlparse``::
|
|
28
|
+
|
|
29
|
+
firebird://host:port//firebird/data/app.fdb → /firebird/data/app.fdb
|
|
30
|
+
|
|
31
|
+
But that double slash is unintuitive to anyone used to the way
|
|
32
|
+
postgres / mysql / mssql encode the database name. We accept five
|
|
33
|
+
equivalent forms and normalise all of them:
|
|
34
|
+
|
|
35
|
+
* ``//abs/path/db.fdb`` → ``/abs/path/db.fdb`` (classic double-slash)
|
|
36
|
+
* ``/abs/path/db.fdb`` → ``/abs/path/db.fdb`` (single-slash, what most people type)
|
|
37
|
+
* ``/C:/Data/db.fdb`` → ``C:/Data/db.fdb`` (Windows, leading URL slash dropped)
|
|
38
|
+
* ``/C%3A/Data/db.fdb`` → ``C:/Data/db.fdb`` (Windows with URL-encoded colon)
|
|
39
|
+
* ``/employee`` → ``employee`` (alias — single token)
|
|
40
|
+
|
|
41
|
+
Aliases are detected as the leftover case: a single token with no
|
|
42
|
+
slashes. Anything path-like is kept as a path.
|
|
43
|
+
"""
|
|
44
|
+
decoded = unquote(raw_path)
|
|
45
|
+
|
|
46
|
+
# Classic double-slash form: //abs/path → /abs/path
|
|
47
|
+
if decoded.startswith("//"):
|
|
48
|
+
decoded = decoded[1:]
|
|
49
|
+
|
|
50
|
+
# Windows drive-letter — drop the URL-introduced leading slash.
|
|
51
|
+
# /C:/Data/db.fdb → C:/Data/db.fdb
|
|
52
|
+
if _WIN_DRIVE_RE.match(decoded):
|
|
53
|
+
if decoded.startswith("/"):
|
|
54
|
+
decoded = decoded[1:]
|
|
55
|
+
return decoded
|
|
56
|
+
|
|
57
|
+
# Look at the content after stripping the leading slash. If it's a
|
|
58
|
+
# single token with no separators, it's a Firebird alias — return
|
|
59
|
+
# WITHOUT the leading slash (the alias name itself is the identifier).
|
|
60
|
+
body = decoded[1:] if decoded.startswith("/") else decoded
|
|
61
|
+
if body and "/" not in body and "\\" not in body:
|
|
62
|
+
return body
|
|
63
|
+
|
|
64
|
+
# Otherwise it's a file path. If it already has a leading slash,
|
|
65
|
+
# keep it. If it's a relative-looking path (slash-separated but no
|
|
66
|
+
# leading "/") promote it to absolute — Firebird needs absolute paths
|
|
67
|
+
# and we don't know the server's CWD anyway.
|
|
68
|
+
return decoded if decoded.startswith("/") else "/" + decoded
|
|
69
|
+
|
|
70
|
+
|
|
13
71
|
# Try modern firebird-driver first, fall back to legacy fdb
|
|
14
72
|
_driver = None
|
|
15
73
|
_driver_name = None
|
|
@@ -28,10 +86,27 @@ except ImportError:
|
|
|
28
86
|
class FirebirdAdapter(DatabaseAdapter):
|
|
29
87
|
"""Firebird database driver using firebird-driver or fdb."""
|
|
30
88
|
|
|
89
|
+
# Substring markers (lowercased) that identify a dead-socket Firebird
|
|
90
|
+
# error worth reconnecting for. Idle Firebird connections die silently
|
|
91
|
+
# behind NAT timeouts, server-side ConnectionIdleTimeout, or Docker
|
|
92
|
+
# network rotation; without this the next prepare() crashes the request.
|
|
93
|
+
_DEAD_CONN_MARKERS = (
|
|
94
|
+
"error writing data to the connection",
|
|
95
|
+
"error reading data from the connection",
|
|
96
|
+
"connection shutdown",
|
|
97
|
+
"connection lost",
|
|
98
|
+
"network error",
|
|
99
|
+
"connection is not active",
|
|
100
|
+
"broken pipe",
|
|
101
|
+
)
|
|
102
|
+
|
|
31
103
|
def __init__(self):
|
|
32
104
|
super().__init__()
|
|
33
105
|
self._conn = None
|
|
34
106
|
self._in_transaction: bool = False
|
|
107
|
+
# Remembered connection params — populated by connect(), used by
|
|
108
|
+
# _reconnect() when a dead socket is detected mid-request.
|
|
109
|
+
self._connect_params: dict | None = None
|
|
35
110
|
|
|
36
111
|
def connect(self, connection_string: str, username: str = "", password: str = "", **kwargs):
|
|
37
112
|
"""Connect to Firebird.
|
|
@@ -48,34 +123,101 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
48
123
|
parsed = urlparse(connection_string)
|
|
49
124
|
host = parsed.hostname or "localhost"
|
|
50
125
|
port = parsed.port or 3050
|
|
51
|
-
|
|
52
|
-
|
|
126
|
+
|
|
127
|
+
# Firebird database identifier resolution — two layers:
|
|
128
|
+
#
|
|
129
|
+
# 1. ``TINA4_DATABASE_FIREBIRD_PATH`` env override wins if set.
|
|
130
|
+
# Useful for Windows users with raw backslash paths (no URL
|
|
131
|
+
# encoding required) and for ops setups that keep server URL
|
|
132
|
+
# and DB location in separate config layers.
|
|
133
|
+
# 2. Otherwise normalise the URL path component — accepts every
|
|
134
|
+
# sensible variant (single/double slash, drive letter, alias).
|
|
135
|
+
env_override = os.environ.get("TINA4_DATABASE_FIREBIRD_PATH", "")
|
|
136
|
+
if env_override:
|
|
137
|
+
db_path = env_override
|
|
138
|
+
else:
|
|
139
|
+
db_path = _normalize_firebird_db_identifier(parsed.path)
|
|
140
|
+
|
|
53
141
|
user = parsed.username or username or "SYSDBA"
|
|
54
142
|
password = parsed.password or password or "masterkey"
|
|
55
143
|
charset = kwargs.pop("charset", "UTF8")
|
|
56
144
|
|
|
145
|
+
# Cache for transparent reconnect — never logged, lives only in
|
|
146
|
+
# adapter memory alongside the connection it owns.
|
|
147
|
+
self._connect_params = {
|
|
148
|
+
"host": host, "port": port, "db_path": db_path,
|
|
149
|
+
"user": user, "password": password, "charset": charset,
|
|
150
|
+
"extra": dict(kwargs),
|
|
151
|
+
}
|
|
152
|
+
self._open()
|
|
153
|
+
|
|
154
|
+
def _open(self) -> None:
|
|
155
|
+
"""Open the underlying Firebird connection from cached params."""
|
|
156
|
+
p = self._connect_params
|
|
157
|
+
if p is None:
|
|
158
|
+
raise RuntimeError("FirebirdAdapter._open called before connect()")
|
|
159
|
+
|
|
57
160
|
if _driver_name == "firebird-driver":
|
|
58
161
|
# Modern firebird-driver uses dsn format: host/port:path
|
|
59
|
-
dsn = f"{host}/{port}:{db_path}" if port != 3050 else f"{host}:{db_path}"
|
|
162
|
+
dsn = f"{p['host']}/{p['port']}:{p['db_path']}" if p['port'] != 3050 else f"{p['host']}:{p['db_path']}"
|
|
60
163
|
self._conn = _driver.connect(
|
|
61
164
|
dsn,
|
|
62
|
-
user=user,
|
|
63
|
-
password=password,
|
|
64
|
-
charset=charset,
|
|
65
|
-
**
|
|
165
|
+
user=p["user"],
|
|
166
|
+
password=p["password"],
|
|
167
|
+
charset=p["charset"],
|
|
168
|
+
**p["extra"],
|
|
66
169
|
)
|
|
67
170
|
else:
|
|
68
171
|
# Legacy fdb
|
|
69
172
|
self._conn = _driver.connect(
|
|
70
|
-
host=host,
|
|
71
|
-
port=port,
|
|
72
|
-
database=db_path,
|
|
73
|
-
user=user,
|
|
74
|
-
password=password,
|
|
75
|
-
charset=charset,
|
|
76
|
-
**
|
|
173
|
+
host=p["host"],
|
|
174
|
+
port=p["port"],
|
|
175
|
+
database=p["db_path"],
|
|
176
|
+
user=p["user"],
|
|
177
|
+
password=p["password"],
|
|
178
|
+
charset=p["charset"],
|
|
179
|
+
**p["extra"],
|
|
77
180
|
)
|
|
78
181
|
|
|
182
|
+
@classmethod
|
|
183
|
+
def _is_dead_connection(cls, exc: BaseException) -> bool:
|
|
184
|
+
"""Match dead-socket error messages from firebird-driver / fdb.
|
|
185
|
+
Substring + case-insensitive so we catch both driver wording variants.
|
|
186
|
+
"""
|
|
187
|
+
msg = str(exc).lower()
|
|
188
|
+
return any(m in msg for m in cls._DEAD_CONN_MARKERS)
|
|
189
|
+
|
|
190
|
+
def _reconnect(self) -> None:
|
|
191
|
+
"""Force-close any stale handle and reopen. Safe to call repeatedly;
|
|
192
|
+
idempotent on a dead connection."""
|
|
193
|
+
try:
|
|
194
|
+
if self._conn is not None:
|
|
195
|
+
self._conn.close()
|
|
196
|
+
except Exception:
|
|
197
|
+
pass # connection already gone — nothing to clean up
|
|
198
|
+
self._conn = None
|
|
199
|
+
self._in_transaction = False
|
|
200
|
+
self._open()
|
|
201
|
+
|
|
202
|
+
def _safe_cursor_execute(self, cursor, sql: str, params: list | None):
|
|
203
|
+
"""Execute on a cursor with one transparent reconnect+retry on
|
|
204
|
+
dead-connection errors. Skipped inside an explicit transaction —
|
|
205
|
+
atomicity beats resilience there; the caller handles rollback.
|
|
206
|
+
|
|
207
|
+
Returns the cursor (possibly a fresh one after reconnect) so the
|
|
208
|
+
caller can fetch results from it.
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
cursor.execute(sql, params or [])
|
|
212
|
+
return cursor
|
|
213
|
+
except Exception as e:
|
|
214
|
+
if not self._is_dead_connection(e) or self._in_transaction:
|
|
215
|
+
raise
|
|
216
|
+
self._reconnect()
|
|
217
|
+
cursor = self._conn.cursor()
|
|
218
|
+
cursor.execute(sql, params or [])
|
|
219
|
+
return cursor
|
|
220
|
+
|
|
79
221
|
def close(self):
|
|
80
222
|
if self._conn:
|
|
81
223
|
self._conn.close()
|
|
@@ -92,7 +234,7 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
92
234
|
sql = sql[:returning_match.start()]
|
|
93
235
|
|
|
94
236
|
cursor = self._conn.cursor()
|
|
95
|
-
cursor.
|
|
237
|
+
cursor = self._safe_cursor_execute(cursor, sql, params)
|
|
96
238
|
|
|
97
239
|
records = []
|
|
98
240
|
last_id = None
|
|
@@ -145,16 +287,19 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
145
287
|
# Count total rows
|
|
146
288
|
count_sql = f"SELECT COUNT(*) FROM ({sql})"
|
|
147
289
|
try:
|
|
148
|
-
cursor.
|
|
290
|
+
cursor = self._safe_cursor_execute(cursor, count_sql, params)
|
|
149
291
|
total = cursor.fetchone()[0]
|
|
150
292
|
except Exception:
|
|
151
293
|
total = 0
|
|
294
|
+
# Reconnect may have just happened — get a fresh cursor for the
|
|
295
|
+
# paginated query below regardless of whether count succeeded.
|
|
296
|
+
cursor = self._conn.cursor()
|
|
152
297
|
|
|
153
298
|
# Apply Firebird pagination — ROWS start TO end
|
|
154
299
|
start = offset + 1
|
|
155
300
|
end = offset + limit
|
|
156
301
|
paginated_sql = f"{sql} ROWS {start} TO {end}"
|
|
157
|
-
cursor.
|
|
302
|
+
cursor = self._safe_cursor_execute(cursor, paginated_sql, params)
|
|
158
303
|
|
|
159
304
|
desc = cursor.description
|
|
160
305
|
col_names = [d[0].strip().lower() for d in desc] if desc else []
|
|
@@ -165,7 +310,7 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
165
310
|
def fetch_one(self, sql: str, params: list = None) -> dict | None:
|
|
166
311
|
sql = self._translate_sql(sql)
|
|
167
312
|
cursor = self._conn.cursor()
|
|
168
|
-
cursor.
|
|
313
|
+
cursor = self._safe_cursor_execute(cursor, sql, params)
|
|
169
314
|
desc = cursor.description
|
|
170
315
|
row = cursor.fetchone()
|
|
171
316
|
if row is None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.12.0 → tina4_python-3.12.2}/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
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/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
|
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/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.12.0 → tina4_python-3.12.2}/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.12.0 → tina4_python-3.12.2}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.0 → tina4_python-3.12.2}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|