tina4-python 3.10.1__tar.gz → 3.10.3__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.10.1 → tina4_python-3.10.3}/PKG-INFO +1 -1
- {tina4_python-3.10.1 → tina4_python-3.10.3}/pyproject.toml +1 -1
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/server.py +94 -31
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/connection.py +7 -4
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/firebird.py +50 -26
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/frond/engine.py +130 -17
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/migration/runner.py +57 -6
- {tina4_python-3.10.1 → tina4_python-3.10.3}/.gitignore +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/README.md +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/Testing.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/events.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/request.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/response.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/core/router.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/dev_reload.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -772,40 +772,98 @@ async def app(scope: dict, receive, send):
|
|
|
772
772
|
request._route_params = params
|
|
773
773
|
request.merge_route_params()
|
|
774
774
|
try:
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if
|
|
775
|
+
# ── Auth enforcement ────────────────────────────────────
|
|
776
|
+
_skip_handler = False
|
|
777
|
+
if route.get("auth_required"):
|
|
778
|
+
_auth_header = request.headers.get("authorization", "")
|
|
779
|
+
_api_key = os.environ.get("TINA4_API_KEY", os.environ.get("API_KEY", ""))
|
|
780
|
+
_auth_ok = False
|
|
781
|
+
if _auth_header:
|
|
782
|
+
if _auth_header.startswith("Bearer "):
|
|
783
|
+
_token = _auth_header[7:]
|
|
784
|
+
# Check static API key first
|
|
785
|
+
if _api_key and _token == _api_key:
|
|
786
|
+
_auth_ok = True
|
|
787
|
+
else:
|
|
788
|
+
# Validate JWT token
|
|
789
|
+
try:
|
|
790
|
+
from tina4_python.auth import Auth
|
|
791
|
+
if Auth.valid_token(_token):
|
|
792
|
+
_auth_ok = True
|
|
793
|
+
except Exception:
|
|
794
|
+
pass
|
|
795
|
+
if not _auth_ok:
|
|
796
|
+
response.status(401).json({
|
|
797
|
+
"error": "Unauthorized",
|
|
798
|
+
"message": "Valid authorization token required",
|
|
799
|
+
"status": 401,
|
|
800
|
+
})
|
|
801
|
+
_skip_handler = True
|
|
802
|
+
|
|
803
|
+
# ── Route middleware (before_* methods) ─────────────────
|
|
804
|
+
if not _skip_handler:
|
|
805
|
+
for _mw_cls in route.get("middleware", []):
|
|
806
|
+
_mw_inst = _mw_cls() if isinstance(_mw_cls, type) else _mw_cls
|
|
807
|
+
for _attr_name in dir(_mw_inst):
|
|
808
|
+
if _attr_name.startswith("before_"):
|
|
809
|
+
_mw_method = getattr(_mw_inst, _attr_name)
|
|
810
|
+
if callable(_mw_method):
|
|
811
|
+
_mw_result = _mw_method(request, response)
|
|
812
|
+
if _mw_result is not None:
|
|
813
|
+
request, response = _mw_result
|
|
814
|
+
# If middleware returned an error status, skip handler
|
|
815
|
+
if response.status_code >= 400:
|
|
816
|
+
_skip_handler = True
|
|
817
|
+
break
|
|
818
|
+
if _skip_handler:
|
|
819
|
+
break
|
|
820
|
+
|
|
821
|
+
if not _skip_handler:
|
|
822
|
+
import inspect
|
|
823
|
+
_sig = inspect.signature(route["handler"])
|
|
824
|
+
_params = list(_sig.parameters.values())
|
|
825
|
+
_pcount = len(_params)
|
|
826
|
+
|
|
827
|
+
# Build args: inject path params by name, then request/response
|
|
828
|
+
_args = []
|
|
829
|
+
_remaining = []
|
|
830
|
+
for p in _params:
|
|
831
|
+
name = p.name
|
|
832
|
+
if name in params:
|
|
833
|
+
_args.append(params[name])
|
|
834
|
+
else:
|
|
835
|
+
_remaining.append(p)
|
|
836
|
+
|
|
837
|
+
# Append request/response for remaining positional params
|
|
838
|
+
if len(_remaining) == 0:
|
|
839
|
+
pass # All params were path params
|
|
840
|
+
elif len(_remaining) == 1:
|
|
841
|
+
_ann = _remaining[0].annotation
|
|
842
|
+
if _ann is Request or (isinstance(_ann, str) and _ann in ("Request", "request")):
|
|
843
|
+
_args.append(request)
|
|
844
|
+
else:
|
|
845
|
+
_args.append(response)
|
|
846
|
+
elif len(_remaining) >= 2:
|
|
796
847
|
_args.append(request)
|
|
797
|
-
else:
|
|
798
848
|
_args.append(response)
|
|
799
|
-
elif len(_remaining) >= 2:
|
|
800
|
-
_args.append(request)
|
|
801
|
-
_args.append(response)
|
|
802
849
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
850
|
+
if _pcount == 0:
|
|
851
|
+
result = await route["handler"]()
|
|
852
|
+
else:
|
|
853
|
+
result = await route["handler"](*_args)
|
|
854
|
+
if isinstance(result, Response):
|
|
855
|
+
response = result
|
|
856
|
+
|
|
857
|
+
# ── Route middleware (after_* methods) ──────────────────
|
|
858
|
+
for _mw_cls in route.get("middleware", []):
|
|
859
|
+
_mw_inst = _mw_cls() if isinstance(_mw_cls, type) else _mw_cls
|
|
860
|
+
for _attr_name in dir(_mw_inst):
|
|
861
|
+
if _attr_name.startswith("after_"):
|
|
862
|
+
_mw_method = getattr(_mw_inst, _attr_name)
|
|
863
|
+
if callable(_mw_method):
|
|
864
|
+
_mw_result = _mw_method(request, response)
|
|
865
|
+
if _mw_result is not None:
|
|
866
|
+
request, response = _mw_result
|
|
809
867
|
except Exception as e:
|
|
810
868
|
Log.error(f"Route error: {e}", path=request.path)
|
|
811
869
|
_write_broken(request, e)
|
|
@@ -1135,6 +1193,11 @@ def run(host: str | None = None, port: int | None = None):
|
|
|
1135
1193
|
global _start_time
|
|
1136
1194
|
_start_time = time.time()
|
|
1137
1195
|
|
|
1196
|
+
# Ensure CWD is on sys.path so auto-discovered modules can be imported
|
|
1197
|
+
cwd = os.getcwd()
|
|
1198
|
+
if cwd not in sys.path:
|
|
1199
|
+
sys.path.insert(0, cwd)
|
|
1200
|
+
|
|
1138
1201
|
# Load .env first so env vars are available for logger init
|
|
1139
1202
|
from tina4_python.dotenv import load_env
|
|
1140
1203
|
load_env()
|
|
@@ -36,12 +36,13 @@ class ConnectionPool:
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
def __init__(self, pool_size: int, factory: callable, connect_path: str,
|
|
39
|
-
username: str = "", password: str = ""):
|
|
39
|
+
username: str = "", password: str = "", **kwargs):
|
|
40
40
|
self._pool_size = pool_size
|
|
41
41
|
self._factory = factory
|
|
42
42
|
self._connect_path = connect_path
|
|
43
43
|
self._username = username
|
|
44
44
|
self._password = password
|
|
45
|
+
self._connect_kwargs = kwargs
|
|
45
46
|
self._adapters: list[DatabaseAdapter | None] = [None] * pool_size
|
|
46
47
|
self._index = 0
|
|
47
48
|
self._lock = threading.Lock()
|
|
@@ -50,7 +51,7 @@ class ConnectionPool:
|
|
|
50
51
|
"""Lazily create an adapter at the given index."""
|
|
51
52
|
if self._adapters[idx] is None:
|
|
52
53
|
adapter = self._factory()
|
|
53
|
-
adapter.connect(self._connect_path, username=self._username, password=self._password)
|
|
54
|
+
adapter.connect(self._connect_path, username=self._username, password=self._password, **self._connect_kwargs)
|
|
54
55
|
self._adapters[idx] = adapter
|
|
55
56
|
return self._adapters[idx]
|
|
56
57
|
|
|
@@ -130,12 +131,13 @@ class Database:
|
|
|
130
131
|
operations to the adapter. This is what the rest of the framework uses.
|
|
131
132
|
"""
|
|
132
133
|
|
|
133
|
-
def __init__(self, url: str = None, username: str = "", password: str = "", pool: int = 0):
|
|
134
|
+
def __init__(self, url: str = None, username: str = "", password: str = "", pool: int = 0, **kwargs):
|
|
134
135
|
self.url = url or os.environ.get("DATABASE_URL", "sqlite:///data/tina4.db")
|
|
135
136
|
# Priority: constructor params > env vars > empty
|
|
136
137
|
self.username = username or os.environ.get("DATABASE_USERNAME", "")
|
|
137
138
|
self.password = password or os.environ.get("DATABASE_PASSWORD", "")
|
|
138
139
|
self.pool_size = pool # 0 = single connection, N>0 = N pooled connections
|
|
140
|
+
self._connect_kwargs = kwargs # Extra kwargs passed through to adapter.connect()
|
|
139
141
|
|
|
140
142
|
if self.pool_size > 0:
|
|
141
143
|
# Pooled mode — create a ConnectionPool with lazy adapter creation
|
|
@@ -145,13 +147,14 @@ class Database:
|
|
|
145
147
|
connect_path=self._connection_path(),
|
|
146
148
|
username=self.username,
|
|
147
149
|
password=self.password,
|
|
150
|
+
**kwargs,
|
|
148
151
|
)
|
|
149
152
|
self._adapter: DatabaseAdapter | None = None
|
|
150
153
|
else:
|
|
151
154
|
# Single-connection mode — current behavior
|
|
152
155
|
self._pool: ConnectionPool | None = None
|
|
153
156
|
self._adapter: DatabaseAdapter = self._create_adapter()
|
|
154
|
-
self._adapter.connect(self._connection_path(), username=self.username, password=self.password)
|
|
157
|
+
self._adapter.connect(self._connection_path(), username=self.username, password=self.password, **kwargs)
|
|
155
158
|
|
|
156
159
|
# Query cache — off by default, opt-in via TINA4_DB_CACHE=true
|
|
157
160
|
from tina4_python.dotenv import is_truthy
|
|
@@ -1,18 +1,32 @@
|
|
|
1
|
-
# Tina4 Firebird Driver — Uses fdb (optional).
|
|
1
|
+
# Tina4 Firebird Driver — Uses firebird-driver or fdb (optional).
|
|
2
2
|
"""
|
|
3
|
-
Firebird adapter using fdb.
|
|
3
|
+
Firebird adapter using firebird-driver (preferred) or fdb (fallback).
|
|
4
4
|
|
|
5
5
|
db = Database("firebird://user:pass@localhost:3050/path/to/database.fdb")
|
|
6
6
|
|
|
7
|
-
Requires: pip install fdb
|
|
7
|
+
Requires: pip install firebird-driver (or pip install fdb for legacy)
|
|
8
8
|
"""
|
|
9
9
|
import re
|
|
10
10
|
from urllib.parse import urlparse, unquote
|
|
11
11
|
from tina4_python.database.adapter import DatabaseAdapter, DatabaseResult, SQLTranslator
|
|
12
12
|
|
|
13
|
+
# Try modern firebird-driver first, fall back to legacy fdb
|
|
14
|
+
_driver = None
|
|
15
|
+
_driver_name = None
|
|
16
|
+
try:
|
|
17
|
+
import firebird.driver as _driver
|
|
18
|
+
_driver_name = "firebird-driver"
|
|
19
|
+
except ImportError:
|
|
20
|
+
try:
|
|
21
|
+
import fdb as _driver
|
|
22
|
+
_driver_name = "fdb"
|
|
23
|
+
except ImportError:
|
|
24
|
+
_driver = None
|
|
25
|
+
_driver_name = None
|
|
26
|
+
|
|
13
27
|
|
|
14
28
|
class FirebirdAdapter(DatabaseAdapter):
|
|
15
|
-
"""Firebird database driver using fdb."""
|
|
29
|
+
"""Firebird database driver using firebird-driver or fdb."""
|
|
16
30
|
|
|
17
31
|
def __init__(self):
|
|
18
32
|
super().__init__()
|
|
@@ -25,12 +39,10 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
25
39
|
Connection string: firebird://user:pass@host:port/path/to/db.fdb
|
|
26
40
|
Credentials priority: URL > username/password params > adapter defaults (SYSDBA/masterkey).
|
|
27
41
|
"""
|
|
28
|
-
|
|
29
|
-
import fdb
|
|
30
|
-
except ImportError:
|
|
42
|
+
if _driver is None:
|
|
31
43
|
raise ImportError(
|
|
32
|
-
"
|
|
33
|
-
"Install: pip install fdb"
|
|
44
|
+
"A Firebird driver is required. "
|
|
45
|
+
"Install: pip install firebird-driver (or pip install fdb for legacy)"
|
|
34
46
|
)
|
|
35
47
|
|
|
36
48
|
parsed = urlparse(connection_string)
|
|
@@ -42,15 +54,27 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
42
54
|
password = parsed.password or password or "masterkey"
|
|
43
55
|
charset = kwargs.pop("charset", "UTF8")
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
host
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
if _driver_name == "firebird-driver":
|
|
58
|
+
# Modern firebird-driver uses dsn format: host/port:path
|
|
59
|
+
dsn = f"{host}/{port}:{db_path}" if port != 3050 else f"{host}:{db_path}"
|
|
60
|
+
self._conn = _driver.connect(
|
|
61
|
+
dsn,
|
|
62
|
+
user=user,
|
|
63
|
+
password=password,
|
|
64
|
+
charset=charset,
|
|
65
|
+
**kwargs,
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
# Legacy fdb
|
|
69
|
+
self._conn = _driver.connect(
|
|
70
|
+
host=host,
|
|
71
|
+
port=port,
|
|
72
|
+
database=db_path,
|
|
73
|
+
user=user,
|
|
74
|
+
password=password,
|
|
75
|
+
charset=charset,
|
|
76
|
+
**kwargs,
|
|
77
|
+
)
|
|
54
78
|
|
|
55
79
|
def close(self):
|
|
56
80
|
if self._conn:
|
|
@@ -94,7 +118,7 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
94
118
|
desc = cursor.description
|
|
95
119
|
row = cursor.fetchone()
|
|
96
120
|
if row and desc:
|
|
97
|
-
col_names = [d[0] for d in desc]
|
|
121
|
+
col_names = [d[0].strip().lower() for d in desc]
|
|
98
122
|
records = [dict(zip(col_names, row))]
|
|
99
123
|
except Exception:
|
|
100
124
|
pass
|
|
@@ -133,7 +157,7 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
133
157
|
cursor.execute(paginated_sql, params or [])
|
|
134
158
|
|
|
135
159
|
desc = cursor.description
|
|
136
|
-
col_names = [d[0] for d in desc] if desc else []
|
|
160
|
+
col_names = [d[0].strip().lower() for d in desc] if desc else []
|
|
137
161
|
rows = [dict(zip(col_names, row)) for row in cursor.fetchall()]
|
|
138
162
|
|
|
139
163
|
return DatabaseResult(records=rows, count=total, sql=sql, adapter=self)
|
|
@@ -146,7 +170,7 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
146
170
|
row = cursor.fetchone()
|
|
147
171
|
if row is None:
|
|
148
172
|
return None
|
|
149
|
-
col_names = [d[0] for d in desc] if desc else []
|
|
173
|
+
col_names = [d[0].strip().lower() for d in desc] if desc else []
|
|
150
174
|
return dict(zip(col_names, row))
|
|
151
175
|
|
|
152
176
|
def insert(self, table: str, data: dict) -> DatabaseResult:
|
|
@@ -204,7 +228,7 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
204
228
|
"ORDER BY RDB$RELATION_NAME",
|
|
205
229
|
limit=10000,
|
|
206
230
|
)
|
|
207
|
-
return [r["
|
|
231
|
+
return [r["rdb$relation_name"].strip() for r in result.records]
|
|
208
232
|
|
|
209
233
|
def get_columns(self, table: str) -> list[dict]:
|
|
210
234
|
sql = (
|
|
@@ -224,10 +248,10 @@ class FirebirdAdapter(DatabaseAdapter):
|
|
|
224
248
|
}
|
|
225
249
|
return [
|
|
226
250
|
{
|
|
227
|
-
"name": r["
|
|
228
|
-
"type": type_map.get(r.get("
|
|
229
|
-
"nullable": r.get("
|
|
230
|
-
"default": r.get("
|
|
251
|
+
"name": r["rdb$field_name"].strip() if r["rdb$field_name"] else "",
|
|
252
|
+
"type": type_map.get(r.get("rdb$field_type"), str(r.get("rdb$field_type", ""))),
|
|
253
|
+
"nullable": r.get("rdb$null_flag") is None,
|
|
254
|
+
"default": r.get("rdb$default_source"),
|
|
231
255
|
"primary_key": False,
|
|
232
256
|
}
|
|
233
257
|
for r in result.records
|
|
@@ -181,6 +181,60 @@ def _find_colon(expr: str) -> int:
|
|
|
181
181
|
# ── Expression Evaluator ────────────────────────────────────────
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
def _split_dotted(expr: str) -> list[str]:
|
|
185
|
+
"""Split a dotted expression into parts, respecting quotes, parens, and brackets.
|
|
186
|
+
|
|
187
|
+
'user.t("auth.email")' → ['user', 't("auth.email")']
|
|
188
|
+
'items[0].name' → ['items', '[0]', 'name']
|
|
189
|
+
'a.b.c' → ['a', 'b', 'c']
|
|
190
|
+
"""
|
|
191
|
+
parts = []
|
|
192
|
+
current = ""
|
|
193
|
+
in_q = None
|
|
194
|
+
paren_depth = 0
|
|
195
|
+
bracket_depth = 0
|
|
196
|
+
i = 0
|
|
197
|
+
while i < len(expr):
|
|
198
|
+
ch = expr[i]
|
|
199
|
+
if ch in ('"', "'") and paren_depth == 0 and bracket_depth == 0 and in_q is None:
|
|
200
|
+
in_q = ch
|
|
201
|
+
current += ch
|
|
202
|
+
elif ch == in_q:
|
|
203
|
+
in_q = None
|
|
204
|
+
current += ch
|
|
205
|
+
elif in_q:
|
|
206
|
+
current += ch
|
|
207
|
+
elif ch == "(":
|
|
208
|
+
paren_depth += 1
|
|
209
|
+
current += ch
|
|
210
|
+
elif ch == ")":
|
|
211
|
+
paren_depth -= 1
|
|
212
|
+
current += ch
|
|
213
|
+
elif ch == "[" and paren_depth == 0:
|
|
214
|
+
# Start of bracket access — save current part if any
|
|
215
|
+
if current:
|
|
216
|
+
parts.append(current)
|
|
217
|
+
current = ""
|
|
218
|
+
bracket_depth += 1
|
|
219
|
+
current += ch
|
|
220
|
+
elif ch == "]" and bracket_depth > 0:
|
|
221
|
+
bracket_depth -= 1
|
|
222
|
+
current += ch
|
|
223
|
+
if bracket_depth == 0:
|
|
224
|
+
parts.append(current)
|
|
225
|
+
current = ""
|
|
226
|
+
elif ch == "." and paren_depth == 0 and bracket_depth == 0:
|
|
227
|
+
if current:
|
|
228
|
+
parts.append(current)
|
|
229
|
+
current = ""
|
|
230
|
+
else:
|
|
231
|
+
current += ch
|
|
232
|
+
i += 1
|
|
233
|
+
if current:
|
|
234
|
+
parts.append(current)
|
|
235
|
+
return parts
|
|
236
|
+
|
|
237
|
+
|
|
184
238
|
def _resolve(expr: str, context: dict):
|
|
185
239
|
"""Resolve a dotted expression against the context.
|
|
186
240
|
|
|
@@ -209,29 +263,59 @@ def _resolve(expr: str, context: dict):
|
|
|
209
263
|
if expr in ("null", "none", "None"):
|
|
210
264
|
return None
|
|
211
265
|
|
|
212
|
-
# Dotted path with bracket access
|
|
213
|
-
parts =
|
|
214
|
-
parts = [p for p in parts if p]
|
|
266
|
+
# Dotted path with bracket access — split respecting quotes and parens
|
|
267
|
+
parts = _split_dotted(expr)
|
|
215
268
|
|
|
216
269
|
value = context
|
|
217
270
|
for part in parts:
|
|
218
271
|
if part.startswith("[") and part.endswith("]"):
|
|
219
272
|
idx = part[1:-1].strip("'\"")
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
273
|
+
# Slice syntax: value[1:5], value[:10], value[3:]
|
|
274
|
+
if ":" in idx:
|
|
275
|
+
slice_parts = idx.split(":", 1)
|
|
276
|
+
s_start = int(slice_parts[0]) if slice_parts[0].strip() else None
|
|
277
|
+
s_end = int(slice_parts[1]) if slice_parts[1].strip() else None
|
|
278
|
+
try:
|
|
279
|
+
value = value[s_start:s_end]
|
|
280
|
+
except (TypeError, IndexError):
|
|
281
|
+
return None
|
|
282
|
+
else:
|
|
283
|
+
try:
|
|
284
|
+
idx = int(idx)
|
|
285
|
+
except ValueError:
|
|
286
|
+
pass
|
|
287
|
+
try:
|
|
288
|
+
value = value[idx]
|
|
289
|
+
except (KeyError, IndexError, TypeError):
|
|
290
|
+
return None
|
|
233
291
|
else:
|
|
234
|
-
|
|
292
|
+
# Check if this part is a method call: name(args)
|
|
293
|
+
call_match = re.match(r"^(\w+)\s*\((.*)?\)$", part, re.DOTALL)
|
|
294
|
+
if call_match:
|
|
295
|
+
method_name = call_match.group(1)
|
|
296
|
+
raw_args = call_match.group(2) or ""
|
|
297
|
+
# Resolve the callable from the current value
|
|
298
|
+
if isinstance(value, dict):
|
|
299
|
+
fn = value.get(method_name)
|
|
300
|
+
elif hasattr(value, method_name):
|
|
301
|
+
fn = getattr(value, method_name)
|
|
302
|
+
else:
|
|
303
|
+
return None
|
|
304
|
+
if callable(fn):
|
|
305
|
+
if raw_args.strip():
|
|
306
|
+
args = [_eval_expr(a.strip(), context) for a in _split_args(raw_args)]
|
|
307
|
+
else:
|
|
308
|
+
args = []
|
|
309
|
+
value = fn(*args)
|
|
310
|
+
else:
|
|
311
|
+
return None
|
|
312
|
+
elif isinstance(value, dict):
|
|
313
|
+
value = value.get(part)
|
|
314
|
+
elif hasattr(value, part):
|
|
315
|
+
attr = getattr(value, part)
|
|
316
|
+
value = attr() if callable(attr) else attr
|
|
317
|
+
else:
|
|
318
|
+
return None
|
|
235
319
|
|
|
236
320
|
if value is None:
|
|
237
321
|
return None
|
|
@@ -239,6 +323,35 @@ def _resolve(expr: str, context: dict):
|
|
|
239
323
|
return value
|
|
240
324
|
|
|
241
325
|
|
|
326
|
+
def _split_args(raw: str) -> list[str]:
|
|
327
|
+
"""Split comma-separated arguments respecting quotes and nested parens."""
|
|
328
|
+
parts = []
|
|
329
|
+
current = ""
|
|
330
|
+
in_q = None
|
|
331
|
+
depth = 0
|
|
332
|
+
for ch in raw:
|
|
333
|
+
if ch in ('"', "'") and not in_q:
|
|
334
|
+
in_q = ch
|
|
335
|
+
current += ch
|
|
336
|
+
elif ch == in_q:
|
|
337
|
+
in_q = None
|
|
338
|
+
current += ch
|
|
339
|
+
elif ch == "(" and not in_q:
|
|
340
|
+
depth += 1
|
|
341
|
+
current += ch
|
|
342
|
+
elif ch == ")" and not in_q:
|
|
343
|
+
depth -= 1
|
|
344
|
+
current += ch
|
|
345
|
+
elif ch == "," and not in_q and depth == 0:
|
|
346
|
+
parts.append(current.strip())
|
|
347
|
+
current = ""
|
|
348
|
+
else:
|
|
349
|
+
current += ch
|
|
350
|
+
if current.strip():
|
|
351
|
+
parts.append(current.strip())
|
|
352
|
+
return parts
|
|
353
|
+
|
|
354
|
+
|
|
242
355
|
def _eval_expr(expr: str, context: dict):
|
|
243
356
|
"""Evaluate a full expression (with ~, ternary, ??, comparisons)."""
|
|
244
357
|
expr = expr.strip()
|
|
@@ -18,7 +18,11 @@ logger = logging.getLogger(__name__)
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def _ensure_tracking_table(db):
|
|
21
|
-
"""Create the migration tracking table
|
|
21
|
+
"""Create or upgrade the migration tracking table.
|
|
22
|
+
|
|
23
|
+
Handles v2→v3 upgrade: v2 tables have `description` but no `migration_id`.
|
|
24
|
+
When detected, adds the missing column and backfills from `description`.
|
|
25
|
+
"""
|
|
22
26
|
if not db.table_exists("tina4_migration"):
|
|
23
27
|
db.execute("""
|
|
24
28
|
CREATE TABLE tina4_migration (
|
|
@@ -31,15 +35,62 @@ def _ensure_tracking_table(db):
|
|
|
31
35
|
)
|
|
32
36
|
""")
|
|
33
37
|
db.commit()
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
# Check if this is a v2 table (has description but no migration_id column)
|
|
41
|
+
try:
|
|
42
|
+
db.fetch_one("SELECT migration_id FROM tina4_migration WHERE 1=0")
|
|
43
|
+
except Exception:
|
|
44
|
+
# migration_id column doesn't exist — v2 schema, upgrade it
|
|
45
|
+
try:
|
|
46
|
+
db.execute("ALTER TABLE tina4_migration ADD migration_id TEXT")
|
|
47
|
+
db.commit()
|
|
48
|
+
except Exception:
|
|
49
|
+
pass # Column may already exist on some engines
|
|
50
|
+
|
|
51
|
+
# Backfill migration_id from description (v2 used description as the identifier)
|
|
52
|
+
try:
|
|
53
|
+
db.execute("UPDATE tina4_migration SET migration_id = description WHERE migration_id IS NULL")
|
|
54
|
+
db.commit()
|
|
55
|
+
except Exception:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
# Add batch column if missing (v2 didn't have it)
|
|
59
|
+
try:
|
|
60
|
+
db.fetch_one("SELECT batch FROM tina4_migration WHERE 1=0")
|
|
61
|
+
except Exception:
|
|
62
|
+
try:
|
|
63
|
+
db.execute("ALTER TABLE tina4_migration ADD batch INTEGER DEFAULT 1")
|
|
64
|
+
db.commit()
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Add executed_at column if missing
|
|
69
|
+
try:
|
|
70
|
+
db.fetch_one("SELECT executed_at FROM tina4_migration WHERE 1=0")
|
|
71
|
+
except Exception:
|
|
72
|
+
try:
|
|
73
|
+
db.execute("ALTER TABLE tina4_migration ADD executed_at TEXT DEFAULT ''")
|
|
74
|
+
db.commit()
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
34
77
|
|
|
35
78
|
|
|
36
79
|
def _get_executed(db) -> set[str]:
|
|
37
80
|
"""Get set of already-executed migration IDs."""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
81
|
+
try:
|
|
82
|
+
result = db.fetch(
|
|
83
|
+
"SELECT migration_id FROM tina4_migration WHERE passed = 1",
|
|
84
|
+
limit=10000,
|
|
85
|
+
)
|
|
86
|
+
return {row["migration_id"] for row in result.records if row.get("migration_id")}
|
|
87
|
+
except Exception:
|
|
88
|
+
# Fallback for v2 tables where migration_id may not exist yet
|
|
89
|
+
result = db.fetch(
|
|
90
|
+
"SELECT description FROM tina4_migration WHERE passed = 1",
|
|
91
|
+
limit=10000,
|
|
92
|
+
)
|
|
93
|
+
return {row["description"] for row in result.records if row.get("description")}
|
|
43
94
|
|
|
44
95
|
|
|
45
96
|
def _get_next_batch(db) -> int:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.10.1 → tina4_python-3.10.3}/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
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/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
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/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.10.1 → tina4_python-3.10.3}/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.10.1 → tina4_python-3.10.3}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.1 → tina4_python-3.10.3}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|