tina4-python 3.13.99__tar.gz → 3.13.100__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.99 → tina4_python-3.13.100}/PKG-INFO +1 -1
- {tina4_python-3.13.99 → tina4_python-3.13.100}/pyproject.toml +1 -1
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/ai/__init__.py +21 -6
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/frond/engine.py +236 -53
- {tina4_python-3.13.99 → tina4_python-3.13.100}/.gitignore +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/README.md +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/context/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/context/chunker.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/port_takeover.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/database_url.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/sql_translator.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/env.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/frond/compiler.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/frond/parser.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/mqtt/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/mqtt/message.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/amqp_url.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/Attachment.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/Channel.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/ChannelMember.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/Message.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/Workspace.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/storage.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/memcached_handler.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -67,14 +67,29 @@ def _skills_ref() -> str:
|
|
|
67
67
|
return "main"
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
def _fetch_bytes(url: str) -> "bytes | None":
|
|
70
|
+
def _fetch_bytes(url: str, attempts: int = 5) -> "bytes | None":
|
|
71
|
+
# raw.githubusercontent.com returns an intermittent 503 under load (a freshly
|
|
72
|
+
# cut tag is "cold" until GitHub warms its CDN), so a single failed fetch out of
|
|
73
|
+
# the ~30 an install makes should not abort it. Retry a transient status
|
|
74
|
+
# (429 / 5xx) and connection errors; a permanent 404 is never retried.
|
|
75
|
+
import time
|
|
71
76
|
import urllib.error
|
|
72
77
|
import urllib.request
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
for attempt in range(attempts):
|
|
79
|
+
try:
|
|
80
|
+
with urllib.request.urlopen(url, timeout=15) as resp:
|
|
81
|
+
return resp.read()
|
|
82
|
+
except urllib.error.HTTPError as exc:
|
|
83
|
+
if exc.code in (429, 500, 502, 503, 504) and attempt < attempts - 1:
|
|
84
|
+
time.sleep(2 * (attempt + 1))
|
|
85
|
+
continue
|
|
86
|
+
return None
|
|
87
|
+
except (urllib.error.URLError, OSError):
|
|
88
|
+
if attempt < attempts - 1:
|
|
89
|
+
time.sleep(2 * (attempt + 1))
|
|
90
|
+
continue
|
|
91
|
+
return None
|
|
92
|
+
return None
|
|
78
93
|
|
|
79
94
|
|
|
80
95
|
def install_skills(root: str = ".", targets: "list[Path] | None" = None) -> list[str]:
|
|
@@ -103,6 +103,77 @@ class _LoopContext(dict):
|
|
|
103
103
|
# found unsupported, use the interpreter) in Frond._compiled_fn.
|
|
104
104
|
_COMPILE_UNSET = object()
|
|
105
105
|
|
|
106
|
+
# Hard cap on the TEMPLATE caches -- _compiled, _compiled_strings and
|
|
107
|
+
# _compiled_fn (ADR-0004, parity with PHP/Ruby/Node TEMPLATE_CACHE_MAX) --
|
|
108
|
+
# and on _fragment_cache (the {% cache %} tag's runtime store: a rendered
|
|
109
|
+
# fragment is a whole HTML string, the same order of magnitude as a compiled
|
|
110
|
+
# template, not a small per-expression descriptor).
|
|
111
|
+
#
|
|
112
|
+
# An entry here is a token list plus an AST plus (for _compiled_fn) an eval'd
|
|
113
|
+
# render closure, orders of magnitude bigger than one expression descriptor,
|
|
114
|
+
# so the cap sits well below MEMO_CACHE_MAX. 256 is far above any real
|
|
115
|
+
# application's template count, so a normal app never evicts. The cap exists
|
|
116
|
+
# for the workloads that genuinely grow without limit for the life of a
|
|
117
|
+
# worker: render_string() keys on md5(source), so an app that builds template
|
|
118
|
+
# strings dynamically adds an entry per distinct string; in dev the
|
|
119
|
+
# compiled-fn key is 'dev:' + md5(source), so every edit to a template file
|
|
120
|
+
# adds one; and a fragment cache keyed on a dynamic value (a page id, a user
|
|
121
|
+
# id) adds one entry per distinct key.
|
|
122
|
+
TEMPLATE_CACHE_MAX = 256
|
|
123
|
+
|
|
124
|
+
# Hard cap on every per-expression memo cache that is NOT already bounded by
|
|
125
|
+
# a module-level ``@lru_cache(maxsize=1024)`` (ADR-0004). ``_split_dotted``,
|
|
126
|
+
# ``_has_low_precedence_op``, ``_expr_descriptor`` and ``_split_on_pipe`` are
|
|
127
|
+
# pure functions and already use that decorator directly; ``_filter_chain_cache``
|
|
128
|
+
# is an INSTANCE dict (its callers are bound methods, and lru_cache on a bound
|
|
129
|
+
# method pins ``self`` alive forever, so it cannot use the decorator) and was
|
|
130
|
+
# left as a plain unbounded dict. Same number as the decorator's cap, applied
|
|
131
|
+
# via the manual helper below -- not a new number, the SAME one this file
|
|
132
|
+
# already uses four times over. Mirrors PHP's MEMO_CACHE_MAX and Ruby's
|
|
133
|
+
# MEMO_CACHE_MAX.
|
|
134
|
+
MEMO_CACHE_MAX = 1024
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _cap_cache(cache: dict, max_entries: int) -> None:
|
|
138
|
+
"""Keep a memo cache bounded. Call immediately before inserting a new entry.
|
|
139
|
+
|
|
140
|
+
Eviction is insertion-ordered (oldest first), not true LRU: a Python dict
|
|
141
|
+
(3.7+) preserves insertion order, so dropping from the front is cheap,
|
|
142
|
+
whereas refreshing recency on every cache HIT would add writes to the
|
|
143
|
+
hottest path in a render and cost more than it saves. Half the cache is
|
|
144
|
+
dropped at once so the sweep amortises to O(1) per insert.
|
|
145
|
+
|
|
146
|
+
Evicting can never change what a render produces: every read site treats
|
|
147
|
+
a miss as "recompute", so a swept entry is rebuilt on next use.
|
|
148
|
+
"""
|
|
149
|
+
if len(cache) < max_entries:
|
|
150
|
+
return
|
|
151
|
+
drop = max_entries // 2
|
|
152
|
+
for key in list(cache.keys())[:drop]:
|
|
153
|
+
del cache[key]
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _sweep_expired_cache(cache: dict) -> None:
|
|
157
|
+
"""Drop every TTL-expired entry from a fragment cache: key -> (html, expires_at).
|
|
158
|
+
|
|
159
|
+
``_cap_cache`` bounds a cache by SIZE (insertion order, oldest first) but
|
|
160
|
+
says nothing about STALENESS: a key that expired and is never visited
|
|
161
|
+
again would otherwise sit in the dict, still counted against the cap,
|
|
162
|
+
until something else finally evicts it. An app keying fragments on a
|
|
163
|
+
dynamic value (a page id, a user id) can churn through many such keys, so
|
|
164
|
+
staleness has to be swept on its own schedule, not just bounded by count.
|
|
165
|
+
|
|
166
|
+
Called on every ``{% cache %}`` render (cheap: bounded by
|
|
167
|
+
TEMPLATE_CACHE_MAX entries, so at most 256 comparisons) rather than only
|
|
168
|
+
for the key being read, so an unrelated key's expiry is cleaned up as a
|
|
169
|
+
side effect of ANY fragment-cache render, not just a future hit on that
|
|
170
|
+
same key.
|
|
171
|
+
"""
|
|
172
|
+
now = time.time()
|
|
173
|
+
expired = [key for key, (_html, expires_at) in cache.items() if expires_at <= now]
|
|
174
|
+
for key in expired:
|
|
175
|
+
del cache[key]
|
|
176
|
+
|
|
106
177
|
# ── Pre-compiled regexes for hot-path operations ───────────────
|
|
107
178
|
_METHOD_CALL_RE = re.compile(r"^(\w+)\s*\((.*)?\)$", re.DOTALL)
|
|
108
179
|
_FUNC_CALL_RE = re.compile(r"^([\w.]+)\s*\((.*)?\)$", re.DOTALL)
|
|
@@ -135,10 +206,39 @@ _SPACELESS_RE = re.compile(r">\s+<")
|
|
|
135
206
|
_STRIPTAGS_RE = re.compile(r"<[^>]+>")
|
|
136
207
|
_SLUG_RE = re.compile(r"[^a-z0-9]+")
|
|
137
208
|
_EXTENDS_RE = re.compile(r"\{%[-\s]*extends\s+[\"'](.+?)[\"']\s*[-]?%\}")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _extends_target(source: str) -> str:
|
|
212
|
+
"""Return this template's OWN ``{% extends %}`` parent name, or ``""``.
|
|
213
|
+
|
|
214
|
+
A template may extend at most one parent. Before 3.13.100 a SECOND
|
|
215
|
+
``{% extends %}`` tag anywhere in the source was silently invisible: only
|
|
216
|
+
the first (leading) occurrence was ever matched, and the rest of the
|
|
217
|
+
child's non-block content -- including the second extends tag -- was
|
|
218
|
+
already discarded the same way ordinary non-block child content is
|
|
219
|
+
discarded during inheritance. That hid what is almost always a mistake
|
|
220
|
+
(a copy-paste, a bad merge) with zero signal. Raise clearly instead, the
|
|
221
|
+
same policy 3.13.89 applied to an unknown tag.
|
|
222
|
+
"""
|
|
223
|
+
matches = _EXTENDS_RE.findall(source)
|
|
224
|
+
if len(matches) > 1:
|
|
225
|
+
raise ValueError(
|
|
226
|
+
f'Frond: template has {len(matches)} "{{% extends %}}" tags -- '
|
|
227
|
+
"a template can extend only one parent"
|
|
228
|
+
)
|
|
229
|
+
match = _EXTENDS_RE.match(source.lstrip())
|
|
230
|
+
return match.group(1) if match else ""
|
|
138
231
|
_BLOCK_RE = re.compile(
|
|
139
232
|
r"\{%[-\s]*block\s+(\w+)\s*[-]?%\}(.*?)\{%[-\s]*endblock\s*[-]?%\}",
|
|
140
233
|
re.DOTALL,
|
|
141
234
|
)
|
|
235
|
+
# Open/close halves of _BLOCK_RE, used standalone by the depth-aware scanners
|
|
236
|
+
# (_extract_blocks, _substitute_blocks) that pair an open tag with its
|
|
237
|
+
# matching close by counting depth rather than the first endblock found --
|
|
238
|
+
# see _substitute_blocks for why a flat single-regex pass truncates a block
|
|
239
|
+
# that nests another block inside it.
|
|
240
|
+
_BLOCK_OPEN_RE = re.compile(r"\{%[-\s]*block\s+(\w+)\s*[-]?%\}")
|
|
241
|
+
_BLOCK_CLOSE_RE = re.compile(r"\{%[-\s]*endblock\s*[-]?%\}")
|
|
142
242
|
|
|
143
243
|
|
|
144
244
|
# ── Ternary helpers ────────────────────────────────────────────
|
|
@@ -1690,14 +1790,13 @@ class Frond:
|
|
|
1690
1790
|
"""Register a custom filter.
|
|
1691
1791
|
|
|
1692
1792
|
Callable as a classmethod (``Frond.add_filter("money", fn)``) or
|
|
1693
|
-
as an instance method (``engine.add_filter("money", fn)``).
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
instance, that instance's local filter map also receives the
|
|
1697
|
-
addition immediately.
|
|
1793
|
+
as an instance method (``engine.add_filter("money", fn)``). Class
|
|
1794
|
+
calls are process-global; instance calls affect that engine only.
|
|
1795
|
+
tina4: ADR-0052.
|
|
1698
1796
|
"""
|
|
1699
|
-
|
|
1700
|
-
|
|
1797
|
+
if instance is None:
|
|
1798
|
+
cls._class_filters[name] = fn
|
|
1799
|
+
else:
|
|
1701
1800
|
instance._filters[name] = fn
|
|
1702
1801
|
|
|
1703
1802
|
@_ClassOrInstanceMethod
|
|
@@ -1707,8 +1806,9 @@ class Frond:
|
|
|
1707
1806
|
Callable as classmethod or instance method. See ``add_filter`` for the
|
|
1708
1807
|
dual-call semantics.
|
|
1709
1808
|
"""
|
|
1710
|
-
|
|
1711
|
-
|
|
1809
|
+
if instance is None:
|
|
1810
|
+
cls._class_globals[name] = value
|
|
1811
|
+
else:
|
|
1712
1812
|
instance._globals[name] = value
|
|
1713
1813
|
|
|
1714
1814
|
@_ClassOrInstanceMethod
|
|
@@ -1718,8 +1818,9 @@ class Frond:
|
|
|
1718
1818
|
Callable as classmethod or instance method. See ``add_filter`` for the
|
|
1719
1819
|
dual-call semantics.
|
|
1720
1820
|
"""
|
|
1721
|
-
|
|
1722
|
-
|
|
1821
|
+
if instance is None:
|
|
1822
|
+
cls._class_tests[name] = fn
|
|
1823
|
+
else:
|
|
1723
1824
|
instance._tests[name] = fn
|
|
1724
1825
|
|
|
1725
1826
|
def render(self, template: str, data: dict = None) -> str:
|
|
@@ -1751,6 +1852,7 @@ class Frond:
|
|
|
1751
1852
|
ast = parse(tokens)
|
|
1752
1853
|
if not debug_mode:
|
|
1753
1854
|
expires_at = (time.time() + self._cache_ttl) if self._cache_ttl > 0 else 0
|
|
1855
|
+
_cap_cache(self._compiled, TEMPLATE_CACHE_MAX)
|
|
1754
1856
|
self._compiled[template] = (tokens, ast, expires_at)
|
|
1755
1857
|
# Prod: the compiled fn is stable per template name (files don't
|
|
1756
1858
|
# change under a running prod worker), so key it by name.
|
|
@@ -1773,6 +1875,7 @@ class Frond:
|
|
|
1773
1875
|
if cached is None:
|
|
1774
1876
|
tokens = _tokenize(source)
|
|
1775
1877
|
cached = (tokens, parse(tokens))
|
|
1878
|
+
_cap_cache(self._compiled_strings, TEMPLATE_CACHE_MAX)
|
|
1776
1879
|
self._compiled_strings[key] = cached
|
|
1777
1880
|
return self._execute_cached(cached[0], cached[1], context, compile_key=key)
|
|
1778
1881
|
|
|
@@ -1836,6 +1939,7 @@ class Frond:
|
|
|
1836
1939
|
return cached
|
|
1837
1940
|
from tina4_python.frond.compiler import compile_template
|
|
1838
1941
|
fn = compile_template(ast)
|
|
1942
|
+
_cap_cache(self._compiled_fn, TEMPLATE_CACHE_MAX)
|
|
1839
1943
|
self._compiled_fn[compile_key] = fn
|
|
1840
1944
|
return fn
|
|
1841
1945
|
|
|
@@ -1871,9 +1975,8 @@ class Frond:
|
|
|
1871
1975
|
template: str = None, compile_key: str = None) -> str:
|
|
1872
1976
|
"""Execute with the source, its tokens and its AST all available."""
|
|
1873
1977
|
# Handle extends first
|
|
1874
|
-
|
|
1875
|
-
if
|
|
1876
|
-
parent_name = extends_match.group(1)
|
|
1978
|
+
parent_name = _extends_target(source)
|
|
1979
|
+
if parent_name:
|
|
1877
1980
|
parent_source = self._load(parent_name)
|
|
1878
1981
|
child_blocks = self._extract_blocks(source)
|
|
1879
1982
|
return self._render_with_blocks(parent_source, context, child_blocks)
|
|
@@ -1886,9 +1989,8 @@ class Frond:
|
|
|
1886
1989
|
def _execute(self, source: str, context: dict) -> str:
|
|
1887
1990
|
"""Execute template source against context."""
|
|
1888
1991
|
# Handle extends first
|
|
1889
|
-
|
|
1890
|
-
if
|
|
1891
|
-
parent_name = extends_match.group(1)
|
|
1992
|
+
parent_name = _extends_target(source)
|
|
1993
|
+
if parent_name:
|
|
1892
1994
|
parent_source = self._load(parent_name)
|
|
1893
1995
|
|
|
1894
1996
|
# Extract blocks from child
|
|
@@ -1906,8 +2008,8 @@ class Frond:
|
|
|
1906
2008
|
a single non-greedy regex (which fails on nested block tags).
|
|
1907
2009
|
"""
|
|
1908
2010
|
blocks = {}
|
|
1909
|
-
block_open =
|
|
1910
|
-
block_close =
|
|
2011
|
+
block_open = _BLOCK_OPEN_RE
|
|
2012
|
+
block_close = _BLOCK_CLOSE_RE
|
|
1911
2013
|
|
|
1912
2014
|
pos = 0
|
|
1913
2015
|
while pos < len(source):
|
|
@@ -1942,6 +2044,101 @@ class Frond:
|
|
|
1942
2044
|
|
|
1943
2045
|
return blocks
|
|
1944
2046
|
|
|
2047
|
+
def _substitute_blocks(self, source: str, blocks: dict, context: dict) -> str:
|
|
2048
|
+
"""Depth-aware block substitution against ``source`` (typically the
|
|
2049
|
+
fully-resolved root template).
|
|
2050
|
+
|
|
2051
|
+
A single non-greedy regex pass (``_BLOCK_RE.sub``) pairs an OUTER
|
|
2052
|
+
block's open tag with the FIRST ``{% endblock %}`` found -- which,
|
|
2053
|
+
when the outer block wraps a NESTED ``{% block %}``, is the nested
|
|
2054
|
+
block's own close tag, not the outer's. That silently truncates the
|
|
2055
|
+
outer block's captured content and drops everything after the inner
|
|
2056
|
+
endblock (the root-nested-block content-loss bug). This scans with
|
|
2057
|
+
an open/close depth counter instead (mirroring ``_extract_blocks``),
|
|
2058
|
+
so an outer block always captures its FULL body, nested child
|
|
2059
|
+
blocks included.
|
|
2060
|
+
|
|
2061
|
+
The content chosen for each block -- the child override in
|
|
2062
|
+
``blocks`` if present, else the block's own default body -- is then
|
|
2063
|
+
recursively substituted against the SAME ``blocks`` map before being
|
|
2064
|
+
tokenized and rendered, so a block nested inside another block
|
|
2065
|
+
resolves correctly regardless of which template in the inheritance
|
|
2066
|
+
chain declared the nesting (the root, an intermediate, however many
|
|
2067
|
+
levels deep).
|
|
2068
|
+
|
|
2069
|
+
``{{ parent() }}`` / ``{{ super() }}`` inside a block still render
|
|
2070
|
+
that block's OWN default content at this level (lazy, on first
|
|
2071
|
+
call) -- unaffected by the depth-aware scan.
|
|
2072
|
+
"""
|
|
2073
|
+
block_open = _BLOCK_OPEN_RE
|
|
2074
|
+
block_close = _BLOCK_CLOSE_RE
|
|
2075
|
+
engine = self
|
|
2076
|
+
length = len(source)
|
|
2077
|
+
pieces = []
|
|
2078
|
+
pos = 0
|
|
2079
|
+
|
|
2080
|
+
while pos < length:
|
|
2081
|
+
m_open = block_open.search(source, pos)
|
|
2082
|
+
if not m_open:
|
|
2083
|
+
pieces.append(source[pos:])
|
|
2084
|
+
break
|
|
2085
|
+
|
|
2086
|
+
pieces.append(source[pos:m_open.start()]) # untouched text before the tag
|
|
2087
|
+
|
|
2088
|
+
name = m_open.group(1)
|
|
2089
|
+
content_start = m_open.end()
|
|
2090
|
+
depth = 1
|
|
2091
|
+
scan = content_start
|
|
2092
|
+
close_match = None
|
|
2093
|
+
|
|
2094
|
+
while depth > 0 and scan < length:
|
|
2095
|
+
next_open = block_open.search(source, scan)
|
|
2096
|
+
next_close = block_close.search(source, scan)
|
|
2097
|
+
|
|
2098
|
+
if next_close is None:
|
|
2099
|
+
break # malformed — no matching endblock
|
|
2100
|
+
|
|
2101
|
+
if next_open and next_open.start() < next_close.start():
|
|
2102
|
+
depth += 1
|
|
2103
|
+
scan = next_open.end()
|
|
2104
|
+
else:
|
|
2105
|
+
depth -= 1
|
|
2106
|
+
if depth == 0:
|
|
2107
|
+
close_match = next_close
|
|
2108
|
+
else:
|
|
2109
|
+
scan = next_close.end()
|
|
2110
|
+
|
|
2111
|
+
if close_match is None:
|
|
2112
|
+
# Malformed template (no matching endblock) — keep the rest
|
|
2113
|
+
# verbatim rather than lose it, the same leniency
|
|
2114
|
+
# _extract_blocks applies to this case.
|
|
2115
|
+
pieces.append(source[m_open.start():])
|
|
2116
|
+
pos = length
|
|
2117
|
+
break
|
|
2118
|
+
|
|
2119
|
+
parent_content = source[content_start:close_match.start()]
|
|
2120
|
+
block_source = blocks.get(name, parent_content)
|
|
2121
|
+
resolved_source = engine._substitute_blocks(block_source, blocks, context)
|
|
2122
|
+
|
|
2123
|
+
rendered_parent = None
|
|
2124
|
+
|
|
2125
|
+
def get_parent(_default=parent_content):
|
|
2126
|
+
nonlocal rendered_parent
|
|
2127
|
+
if rendered_parent is None:
|
|
2128
|
+
rendered_parent = SafeString(
|
|
2129
|
+
engine._render_tokens(_tokenize(_default), context)
|
|
2130
|
+
)
|
|
2131
|
+
return rendered_parent
|
|
2132
|
+
|
|
2133
|
+
block_ctx = dict(context)
|
|
2134
|
+
block_ctx["parent"] = get_parent
|
|
2135
|
+
block_ctx["super"] = get_parent
|
|
2136
|
+
|
|
2137
|
+
pieces.append(engine._render_tokens(_tokenize(resolved_source), block_ctx))
|
|
2138
|
+
pos = close_match.end()
|
|
2139
|
+
|
|
2140
|
+
return "".join(pieces)
|
|
2141
|
+
|
|
1945
2142
|
def _render_with_blocks(self, parent_source: str, context: dict, child_blocks: dict) -> str:
|
|
1946
2143
|
"""Render parent template, replacing blocks with child content.
|
|
1947
2144
|
|
|
@@ -1954,9 +2151,8 @@ class Frond:
|
|
|
1954
2151
|
the parent block's content (standard Twig/Jinja2 behavior).
|
|
1955
2152
|
"""
|
|
1956
2153
|
# --- Multi-level extends: check if parent itself extends a grandparent ---
|
|
1957
|
-
|
|
1958
|
-
if
|
|
1959
|
-
grandparent_name = extends_match.group(1)
|
|
2154
|
+
grandparent_name = _extends_target(parent_source)
|
|
2155
|
+
if grandparent_name:
|
|
1960
2156
|
grandparent_source = self._load(grandparent_name)
|
|
1961
2157
|
|
|
1962
2158
|
# Extract block defaults defined in the parent template
|
|
@@ -1984,36 +2180,10 @@ class Frond:
|
|
|
1984
2180
|
return self._render_with_blocks(grandparent_source, context, merged_blocks)
|
|
1985
2181
|
|
|
1986
2182
|
# --- Leaf parent (no extends) — resolve blocks and render ---
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
parent_content = m.group(2)
|
|
1992
|
-
block_source = child_blocks.get(name, parent_content)
|
|
1993
|
-
|
|
1994
|
-
# Make parent() and super() available inside child blocks
|
|
1995
|
-
# They return the rendered parent block content
|
|
1996
|
-
rendered_parent = None
|
|
1997
|
-
|
|
1998
|
-
def get_parent():
|
|
1999
|
-
nonlocal rendered_parent
|
|
2000
|
-
if rendered_parent is None:
|
|
2001
|
-
rendered_parent = SafeString(
|
|
2002
|
-
engine._render_tokens(_tokenize(parent_content), context)
|
|
2003
|
-
)
|
|
2004
|
-
return rendered_parent
|
|
2005
|
-
|
|
2006
|
-
# Inject parent/super into a block-local context
|
|
2007
|
-
block_ctx = dict(context)
|
|
2008
|
-
block_ctx["parent"] = get_parent
|
|
2009
|
-
block_ctx["super"] = get_parent
|
|
2010
|
-
|
|
2011
|
-
return engine._render_tokens(_tokenize(block_source), block_ctx)
|
|
2012
|
-
|
|
2013
|
-
pattern = _BLOCK_RE
|
|
2014
|
-
|
|
2015
|
-
# First pass: replace blocks
|
|
2016
|
-
result = pattern.sub(replace_block, parent_source)
|
|
2183
|
+
# First pass: depth-aware block substitution (handles a block nested
|
|
2184
|
+
# inside another block at ANY level of the chain, including the root
|
|
2185
|
+
# itself — see _substitute_blocks).
|
|
2186
|
+
result = self._substitute_blocks(parent_source, child_blocks, context)
|
|
2017
2187
|
# Second pass: render remaining tokens (text, vars outside blocks)
|
|
2018
2188
|
return self._render_tokens(_tokenize(result), context)
|
|
2019
2189
|
|
|
@@ -2167,11 +2337,21 @@ class Frond:
|
|
|
2167
2337
|
return self._eval_var_inner(expr, context)
|
|
2168
2338
|
|
|
2169
2339
|
def _cached_filter_chain(self, expr: str):
|
|
2170
|
-
"""Return parsed filter chain from cache, or parse and cache.
|
|
2340
|
+
"""Return parsed filter chain from cache, or parse and cache.
|
|
2341
|
+
|
|
2342
|
+
``self._filter_chain_cache`` is an INSTANCE dict (this is a bound
|
|
2343
|
+
method), so it cannot use the module-level ``@lru_cache(maxsize=1024)``
|
|
2344
|
+
the sibling pure-function parsers use (that decorator on a bound
|
|
2345
|
+
method pins ``self`` alive for the life of the cache). Bounded here
|
|
2346
|
+
with the same MEMO_CACHE_MAX via the manual cap helper instead
|
|
2347
|
+
(ADR-0004) — a template that builds expression strings dynamically
|
|
2348
|
+
must not grow this dict without limit for the life of a worker.
|
|
2349
|
+
"""
|
|
2171
2350
|
cached = self._filter_chain_cache.get(expr)
|
|
2172
2351
|
if cached is not None:
|
|
2173
2352
|
return cached
|
|
2174
2353
|
result = _parse_filter_chain(expr)
|
|
2354
|
+
_cap_cache(self._filter_chain_cache, MEMO_CACHE_MAX)
|
|
2175
2355
|
self._filter_chain_cache[expr] = result
|
|
2176
2356
|
return result
|
|
2177
2357
|
|
|
@@ -2603,6 +2783,8 @@ class Frond:
|
|
|
2603
2783
|
cache_key = m.group(1) if m else "default"
|
|
2604
2784
|
ttl = int(m.group(2)) if m and m.group(2) else 60
|
|
2605
2785
|
|
|
2786
|
+
_sweep_expired_cache(self._fragment_cache)
|
|
2787
|
+
|
|
2606
2788
|
# Check cache — a live entry means the body is never rendered
|
|
2607
2789
|
cached = self._fragment_cache.get(cache_key)
|
|
2608
2790
|
if cached:
|
|
@@ -2612,6 +2794,7 @@ class Frond:
|
|
|
2612
2794
|
|
|
2613
2795
|
# Render and cache
|
|
2614
2796
|
rendered = self._render_nodes(node.body, context)
|
|
2797
|
+
_cap_cache(self._fragment_cache, TEMPLATE_CACHE_MAX)
|
|
2615
2798
|
self._fragment_cache[cache_key] = (rendered, time.time() + ttl)
|
|
2616
2799
|
return rendered
|
|
2617
2800
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.99 → tina4_python-3.13.100}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/public/js/tina4-dev-admin.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/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
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/realtime/models/ChannelMember.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
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/memcached_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.99 → tina4_python-3.13.100}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|