tina4-python 3.11.8__tar.gz → 3.11.9__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.11.8 → tina4_python-3.11.9}/PKG-INFO +1 -1
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/__init__.py +1 -1
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/frond/engine.py +7 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/session_handlers/mongodb_handler.py +2 -2
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/session_handlers/redis_handler.py +2 -2
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/session_handlers/valkey_handler.py +2 -2
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/websocket/__init__.py +18 -2
- tina4_python-3.11.8/tina4_python/dev_reload.py +0 -214
- {tina4_python-3.11.8 → tina4_python-3.11.9}/.gitignore +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/README.md +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/pyproject.toml +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/Testing.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/events.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/request.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/response.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/router.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/core/server.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -414,6 +414,13 @@ def _resolve(expr: str, context: dict):
|
|
|
414
414
|
return None
|
|
415
415
|
elif isinstance(value, dict):
|
|
416
416
|
value = value.get(part)
|
|
417
|
+
elif isinstance(value, (list, tuple)) and part.isdigit():
|
|
418
|
+
# Numeric dot-index into a list/tuple: items.0.name
|
|
419
|
+
idx = int(part)
|
|
420
|
+
try:
|
|
421
|
+
value = value[idx]
|
|
422
|
+
except IndexError:
|
|
423
|
+
return None
|
|
417
424
|
elif hasattr(value, part):
|
|
418
425
|
attr = getattr(value, part)
|
|
419
426
|
value = attr() if callable(attr) else attr
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
@@ -7,7 +7,7 @@ Environment variables:
|
|
|
7
7
|
TINA4_SESSION_MONGO_URL — MongoDB URL (default: mongodb://localhost:27017)
|
|
8
8
|
TINA4_SESSION_MONGO_DB — database name (default: tina4)
|
|
9
9
|
TINA4_SESSION_MONGO_COLLECTION — collection name (default: sessions)
|
|
10
|
-
TINA4_SESSION_TTL — session TTL in seconds (default:
|
|
10
|
+
TINA4_SESSION_TTL — session TTL in seconds (default: 3600)
|
|
11
11
|
"""
|
|
12
12
|
import json
|
|
13
13
|
import os
|
|
@@ -28,7 +28,7 @@ class MongoDBSessionHandler(SessionHandler):
|
|
|
28
28
|
mongo_url = config.get("url", os.environ.get("TINA4_SESSION_MONGO_URL", "mongodb://localhost:27017"))
|
|
29
29
|
self._database = config.get("database", os.environ.get("TINA4_SESSION_MONGO_DB", "tina4"))
|
|
30
30
|
self._collection_name = config.get("collection", os.environ.get("TINA4_SESSION_MONGO_COLLECTION", "sessions"))
|
|
31
|
-
self._ttl = int(config.get("ttl", os.environ.get("TINA4_SESSION_TTL", "
|
|
31
|
+
self._ttl = int(config.get("ttl", os.environ.get("TINA4_SESSION_TTL", "3600")))
|
|
32
32
|
|
|
33
33
|
self._pymongo_client = None
|
|
34
34
|
self._collection = None
|
|
@@ -8,7 +8,7 @@ Environment variables:
|
|
|
8
8
|
TINA4_SESSION_REDIS_PORT — port (default: 6379)
|
|
9
9
|
TINA4_SESSION_REDIS_PASSWORD — password (default: none)
|
|
10
10
|
TINA4_SESSION_REDIS_DB — database number (default: 0)
|
|
11
|
-
TINA4_SESSION_TTL — session TTL in seconds (default:
|
|
11
|
+
TINA4_SESSION_TTL — session TTL in seconds (default: 3600)
|
|
12
12
|
"""
|
|
13
13
|
import json
|
|
14
14
|
import os
|
|
@@ -28,7 +28,7 @@ class RedisSessionHandler(SessionHandler):
|
|
|
28
28
|
self._port = int(config.get("port", os.environ.get("TINA4_SESSION_REDIS_PORT", "6379")))
|
|
29
29
|
self._password = config.get("password") or os.environ.get("TINA4_SESSION_REDIS_PASSWORD") or None
|
|
30
30
|
self._db = int(config.get("db", os.environ.get("TINA4_SESSION_REDIS_DB", "0")))
|
|
31
|
-
self._ttl = int(config.get("ttl", os.environ.get("TINA4_SESSION_TTL", "
|
|
31
|
+
self._ttl = int(config.get("ttl", os.environ.get("TINA4_SESSION_TTL", "3600")))
|
|
32
32
|
self._prefix = config.get("prefix", "tina4:session:")
|
|
33
33
|
|
|
34
34
|
self._redis_client = None
|
|
@@ -9,7 +9,7 @@ Environment variables:
|
|
|
9
9
|
TINA4_SESSION_VALKEY_PORT — port (default: 6379)
|
|
10
10
|
TINA4_SESSION_VALKEY_PASSWORD — password (default: none)
|
|
11
11
|
TINA4_SESSION_VALKEY_DB — database number (default: 0)
|
|
12
|
-
TINA4_SESSION_TTL — session TTL in seconds (default:
|
|
12
|
+
TINA4_SESSION_TTL — session TTL in seconds (default: 3600)
|
|
13
13
|
"""
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
@@ -30,7 +30,7 @@ class ValkeySessionHandler(SessionHandler):
|
|
|
30
30
|
self._port = int(config.get("port", os.environ.get("TINA4_SESSION_VALKEY_PORT", "6379")))
|
|
31
31
|
self._password = config.get("password") or os.environ.get("TINA4_SESSION_VALKEY_PASSWORD") or None
|
|
32
32
|
self._db = int(config.get("db", os.environ.get("TINA4_SESSION_VALKEY_DB", "0")))
|
|
33
|
-
self._ttl = int(config.get("ttl", os.environ.get("TINA4_SESSION_TTL", "
|
|
33
|
+
self._ttl = int(config.get("ttl", os.environ.get("TINA4_SESSION_TTL", "3600")))
|
|
34
34
|
self._prefix = config.get("prefix", "tina4:session:")
|
|
35
35
|
|
|
36
36
|
self._redis_client = None
|
|
@@ -418,6 +418,15 @@ class WebSocketManager:
|
|
|
418
418
|
ids = self._rooms.get(room_name, set())
|
|
419
419
|
return [self._connections[i] for i in ids if i in self._connections]
|
|
420
420
|
|
|
421
|
+
def get_client_rooms(self, client_id: str) -> list[str]:
|
|
422
|
+
"""Return the list of room names a specific client belongs to.
|
|
423
|
+
|
|
424
|
+
Mirrors PHP's ``getClientRooms()`` and the per-connection ``conn.rooms``
|
|
425
|
+
property — useful when you have a client ID but not the connection
|
|
426
|
+
object itself.
|
|
427
|
+
"""
|
|
428
|
+
return [room for room, members in self._rooms.items() if client_id in members]
|
|
429
|
+
|
|
421
430
|
async def broadcast_to_room(self, room_name: str, message: str | bytes,
|
|
422
431
|
exclude: str = None) -> None:
|
|
423
432
|
"""Send message to all connections in a room."""
|
|
@@ -437,8 +446,12 @@ class WebSocketServer:
|
|
|
437
446
|
self._handlers: dict[str, dict[str, Callable]] = {}
|
|
438
447
|
self._server: asyncio.AbstractServer | None = None
|
|
439
448
|
|
|
440
|
-
def route(self, path: str):
|
|
441
|
-
"""
|
|
449
|
+
def route(self, path: str, handler: Callable | None = None):
|
|
450
|
+
"""Register a WebSocket handler for a path.
|
|
451
|
+
|
|
452
|
+
Can be used either as a decorator (``@server.route("/chat")``) or
|
|
453
|
+
called directly with a handler (``server.route("/chat", chat_handler)``)
|
|
454
|
+
for parity with PHP/Ruby/Node.
|
|
442
455
|
|
|
443
456
|
Registers both on this server instance (standalone mode) and on the
|
|
444
457
|
main Router (integrated mode) so routes work either way.
|
|
@@ -471,6 +484,9 @@ class WebSocketServer:
|
|
|
471
484
|
from tina4_python.core.router import Router
|
|
472
485
|
Router.websocket(path, _router_adapter)
|
|
473
486
|
return func
|
|
487
|
+
|
|
488
|
+
if handler is not None:
|
|
489
|
+
return decorator(handler)
|
|
474
490
|
return decorator
|
|
475
491
|
|
|
476
492
|
def on_connect(self, path: str):
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Tina4 DevReload — File-change detection via mtime polling.
|
|
2
|
-
"""
|
|
3
|
-
Watches source files for changes and triggers route re-discovery.
|
|
4
|
-
Active only when TINA4_DEBUG=true.
|
|
5
|
-
|
|
6
|
-
The browser-side polling is handled by JS injected into the dev toolbar,
|
|
7
|
-
which polls /__dev/api/mtime and reloads when the timestamp changes.
|
|
8
|
-
|
|
9
|
-
Uses simple mtime polling (no external dependencies).
|
|
10
|
-
"""
|
|
11
|
-
import os
|
|
12
|
-
import sys
|
|
13
|
-
import time
|
|
14
|
-
import importlib
|
|
15
|
-
import threading
|
|
16
|
-
from pathlib import Path
|
|
17
|
-
|
|
18
|
-
from tina4_python.debug import Log
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
# Watched file extensions
|
|
22
|
-
_WATCH_EXTENSIONS = {".py", ".twig", ".html", ".css", ".scss", ".js"}
|
|
23
|
-
|
|
24
|
-
# Directories to ignore (anywhere in the path)
|
|
25
|
-
_IGNORE_DIRS = {".git", "node_modules", "vendor", "__pycache__", "data", ".venv", ".mypy_cache", ".ruff_cache"}
|
|
26
|
-
|
|
27
|
-
# Module-level state
|
|
28
|
-
_last_mtime: float = 0.0
|
|
29
|
-
_last_change_file: str = ""
|
|
30
|
-
_lock = threading.Lock()
|
|
31
|
-
_running = False
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def get_last_mtime() -> float:
|
|
35
|
-
"""Return the most recent file modification timestamp."""
|
|
36
|
-
return _last_mtime
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def get_last_change_file() -> str:
|
|
40
|
-
"""Return the path of the most recently changed file."""
|
|
41
|
-
return _last_change_file
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _should_ignore(path: Path) -> bool:
|
|
45
|
-
"""Check if a path should be ignored based on directory names."""
|
|
46
|
-
for part in path.parts:
|
|
47
|
-
if part in _IGNORE_DIRS:
|
|
48
|
-
return True
|
|
49
|
-
return False
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def _scan_mtime(directories: list[str]) -> tuple[float, str]:
|
|
53
|
-
"""Scan directories for the maximum file mtime.
|
|
54
|
-
|
|
55
|
-
Returns (max_mtime, file_path) tuple.
|
|
56
|
-
"""
|
|
57
|
-
max_mtime = 0.0
|
|
58
|
-
max_file = ""
|
|
59
|
-
|
|
60
|
-
for dir_path in directories:
|
|
61
|
-
root = Path(dir_path)
|
|
62
|
-
if not root.is_dir():
|
|
63
|
-
continue
|
|
64
|
-
|
|
65
|
-
for file_path in root.rglob("*"):
|
|
66
|
-
if not file_path.is_file():
|
|
67
|
-
continue
|
|
68
|
-
if file_path.suffix not in _WATCH_EXTENSIONS:
|
|
69
|
-
continue
|
|
70
|
-
if _should_ignore(file_path):
|
|
71
|
-
continue
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
mtime = file_path.stat().st_mtime
|
|
75
|
-
if mtime > max_mtime:
|
|
76
|
-
max_mtime = mtime
|
|
77
|
-
max_file = str(file_path)
|
|
78
|
-
except OSError:
|
|
79
|
-
continue
|
|
80
|
-
|
|
81
|
-
return max_mtime, max_file
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _rediscover_routes():
|
|
85
|
-
"""Re-import changed Python modules in src/ to pick up new/changed routes.
|
|
86
|
-
|
|
87
|
-
Clears the route registry and re-discovers all routes from scratch.
|
|
88
|
-
This ensures removed routes are also cleaned up.
|
|
89
|
-
"""
|
|
90
|
-
from tina4_python.core.router import Router, _routes
|
|
91
|
-
|
|
92
|
-
# Remember route count before
|
|
93
|
-
before = len(_routes)
|
|
94
|
-
|
|
95
|
-
# Reload all src/ modules that are already in sys.modules
|
|
96
|
-
root = Path("src").resolve()
|
|
97
|
-
if not root.is_dir():
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
skip = {"public", "templates", "scss", "locales", "icons"}
|
|
101
|
-
reloaded = 0
|
|
102
|
-
|
|
103
|
-
# Clear existing routes (they'll be re-registered on reload)
|
|
104
|
-
_routes.clear()
|
|
105
|
-
|
|
106
|
-
for py_file in sorted(root.rglob("*.py")):
|
|
107
|
-
if any(part.startswith("_") for part in py_file.parts):
|
|
108
|
-
continue
|
|
109
|
-
if any(s in py_file.parts for s in skip):
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
|
-
try:
|
|
113
|
-
rel = py_file.relative_to(Path.cwd()).with_suffix("")
|
|
114
|
-
module_name = ".".join(rel.parts)
|
|
115
|
-
|
|
116
|
-
if module_name in sys.modules:
|
|
117
|
-
# Reload existing module
|
|
118
|
-
importlib.reload(sys.modules[module_name])
|
|
119
|
-
reloaded += 1
|
|
120
|
-
else:
|
|
121
|
-
# Import new module
|
|
122
|
-
importlib.import_module(module_name)
|
|
123
|
-
reloaded += 1
|
|
124
|
-
except Exception as e:
|
|
125
|
-
Log.error(f"DevReload: failed to reload {py_file}: {e}")
|
|
126
|
-
|
|
127
|
-
# Re-register built-in routes (health check)
|
|
128
|
-
from tina4_python.core.server import _health_handler
|
|
129
|
-
Router.add("GET", "/health", _health_handler)
|
|
130
|
-
|
|
131
|
-
after = len(_routes)
|
|
132
|
-
Log.debug(f"DevReload: reloaded {reloaded} modules, {before} -> {after} routes")
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _poll_loop(directories: list[str], interval: float = 1.0):
|
|
136
|
-
"""Background thread that polls file mtimes and triggers re-discovery."""
|
|
137
|
-
global _last_mtime, _last_change_file, _running
|
|
138
|
-
|
|
139
|
-
# Initial scan
|
|
140
|
-
with _lock:
|
|
141
|
-
_last_mtime, _last_change_file = _scan_mtime(directories)
|
|
142
|
-
|
|
143
|
-
Log.debug(f"DevReload: watching {', '.join(directories)} "
|
|
144
|
-
f"(extensions: {', '.join(sorted(_WATCH_EXTENSIONS))})")
|
|
145
|
-
|
|
146
|
-
while _running:
|
|
147
|
-
time.sleep(interval)
|
|
148
|
-
|
|
149
|
-
new_mtime, new_file = _scan_mtime(directories)
|
|
150
|
-
|
|
151
|
-
if new_mtime > _last_mtime:
|
|
152
|
-
rel_path = new_file
|
|
153
|
-
try:
|
|
154
|
-
rel_path = str(Path(new_file).relative_to(Path.cwd()))
|
|
155
|
-
except ValueError:
|
|
156
|
-
pass
|
|
157
|
-
|
|
158
|
-
Log.info(f"DevReload: change detected in {rel_path}")
|
|
159
|
-
|
|
160
|
-
with _lock:
|
|
161
|
-
_last_mtime = new_mtime
|
|
162
|
-
_last_change_file = new_file
|
|
163
|
-
|
|
164
|
-
# Re-discover routes if a Python file changed
|
|
165
|
-
if new_file.endswith(".py"):
|
|
166
|
-
try:
|
|
167
|
-
_rediscover_routes()
|
|
168
|
-
except Exception as e:
|
|
169
|
-
Log.error(f"DevReload: route re-discovery failed: {e}")
|
|
170
|
-
|
|
171
|
-
# Note: SCSS compilation is handled by the tina4 CLI (Rust).
|
|
172
|
-
# DevReload only handles route re-discovery and browser refresh.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def start(directories: list[str] | None = None, interval: float | None = None):
|
|
176
|
-
"""Start the DevReload file watcher in a background thread.
|
|
177
|
-
|
|
178
|
-
Args:
|
|
179
|
-
directories: List of directories to watch. Defaults to ["src", "public"].
|
|
180
|
-
interval: Polling interval in seconds. Defaults to TINA4_DEV_POLL_INTERVAL/1000
|
|
181
|
-
env var (milliseconds), or 3.0 seconds if not set.
|
|
182
|
-
"""
|
|
183
|
-
global _running
|
|
184
|
-
|
|
185
|
-
if _running:
|
|
186
|
-
return
|
|
187
|
-
|
|
188
|
-
if directories is None:
|
|
189
|
-
directories = ["src", "public"]
|
|
190
|
-
|
|
191
|
-
if interval is None:
|
|
192
|
-
env_ms = os.environ.get("TINA4_DEV_POLL_INTERVAL", "3000")
|
|
193
|
-
try:
|
|
194
|
-
interval = max(0.5, int(env_ms) / 1000.0)
|
|
195
|
-
except ValueError:
|
|
196
|
-
interval = 3.0
|
|
197
|
-
|
|
198
|
-
_running = True
|
|
199
|
-
|
|
200
|
-
thread = threading.Thread(
|
|
201
|
-
target=_poll_loop,
|
|
202
|
-
args=(directories, interval),
|
|
203
|
-
daemon=True,
|
|
204
|
-
name="tina4-dev-reload",
|
|
205
|
-
)
|
|
206
|
-
thread.start()
|
|
207
|
-
Log.info(f"DevReload: file watcher started (interval={interval:.1f}s)")
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def stop():
|
|
211
|
-
"""Stop the DevReload file watcher."""
|
|
212
|
-
global _running
|
|
213
|
-
_running = False
|
|
214
|
-
Log.debug("DevReload: file watcher stopped")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.11.8 → tina4_python-3.11.9}/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
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/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
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/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.11.8 → tina4_python-3.11.9}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.11.8 → tina4_python-3.11.9}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|