tina4-python 3.13.0__tar.gz → 3.13.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-3.13.0 → tina4_python-3.13.2}/PKG-INFO +1 -1
- {tina4_python-3.13.0 → tina4_python-3.13.2}/pyproject.toml +1 -1
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/connection.py +3 -2
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/dev_admin/__init__.py +8 -4
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/orm/model.py +2 -2
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/__init__.py +65 -27
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/service/__init__.py +103 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/.gitignore +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/README.md +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -725,8 +725,9 @@ class Database:
|
|
|
725
725
|
def from_env(env_key: str = "TINA4_DATABASE_URL", pool: int = 0) -> "Database | None":
|
|
726
726
|
"""Construct a Database instance from environment variables.
|
|
727
727
|
|
|
728
|
-
Reads the connection URL from the named env var (default
|
|
729
|
-
and
|
|
728
|
+
Reads the connection URL from the named env var (default
|
|
729
|
+
TINA4_DATABASE_URL), and TINA4_DATABASE_USERNAME /
|
|
730
|
+
TINA4_DATABASE_PASSWORD for credentials.
|
|
730
731
|
|
|
731
732
|
Args:
|
|
732
733
|
env_key: Environment variable name holding the connection URL.
|
|
@@ -1743,7 +1743,7 @@ def _tina4_robot_fallback(message: str) -> str:
|
|
|
1743
1743
|
elif "orm" in msg or "model" in msg:
|
|
1744
1744
|
return "Define ORM models in src/orm/ — one class per file. Use IntegerField, StringField, etc. Call model.save(), model.load(), model.select(). Don't forget to create a migration for the table."
|
|
1745
1745
|
elif "database" in msg or "db" in msg:
|
|
1746
|
-
return "Set
|
|
1746
|
+
return "Set TINA4_DATABASE_URL in .env. Supports sqlite, postgres, mysql, firebird, mssql, mongodb. Use db.fetch(), db.insert(), db.update(), db.delete()."
|
|
1747
1747
|
elif "queue" in msg:
|
|
1748
1748
|
return "Use Queue(topic='name') with queue.produce() to enqueue, queue.consume() to process. Supports litequeue, RabbitMQ, Kafka, MongoDB backends."
|
|
1749
1749
|
elif "template" in msg or "twig" in msg:
|
|
@@ -1848,14 +1848,18 @@ async def _api_connections_save(request, response):
|
|
|
1848
1848
|
new_lines.append(line)
|
|
1849
1849
|
continue
|
|
1850
1850
|
key = stripped.split("=", 1)[0].strip()
|
|
1851
|
+
# Preserve the TINA4_ prefix when rewriting — the boot guard
|
|
1852
|
+
# rejects bare DATABASE_URL / DATABASE_USERNAME / DATABASE_PASSWORD
|
|
1853
|
+
# since v3.12, so stripping the prefix here would break the next
|
|
1854
|
+
# restart. Bug filed as tina4-python#45.
|
|
1851
1855
|
if key == "TINA4_DATABASE_URL":
|
|
1852
|
-
new_lines.append(f"
|
|
1856
|
+
new_lines.append(f"TINA4_DATABASE_URL={url}")
|
|
1853
1857
|
keys_found["TINA4_DATABASE_URL"] = True
|
|
1854
1858
|
elif key == "TINA4_DATABASE_USERNAME":
|
|
1855
|
-
new_lines.append(f"
|
|
1859
|
+
new_lines.append(f"TINA4_DATABASE_USERNAME={username}")
|
|
1856
1860
|
keys_found["TINA4_DATABASE_USERNAME"] = True
|
|
1857
1861
|
elif key == "TINA4_DATABASE_PASSWORD":
|
|
1858
|
-
new_lines.append(f"
|
|
1862
|
+
new_lines.append(f"TINA4_DATABASE_PASSWORD={password}")
|
|
1859
1863
|
keys_found["TINA4_DATABASE_PASSWORD"] = True
|
|
1860
1864
|
else:
|
|
1861
1865
|
new_lines.append(line)
|
|
@@ -278,7 +278,7 @@ class ORM(metaclass=ORMMeta):
|
|
|
278
278
|
return cls._db # Direct Database instance
|
|
279
279
|
|
|
280
280
|
if _database is None:
|
|
281
|
-
# Try auto-discovery from
|
|
281
|
+
# Try auto-discovery from TINA4_DATABASE_URL
|
|
282
282
|
import os
|
|
283
283
|
url = os.environ.get("TINA4_DATABASE_URL")
|
|
284
284
|
if url:
|
|
@@ -289,7 +289,7 @@ class ORM(metaclass=ORMMeta):
|
|
|
289
289
|
orm_bind(db)
|
|
290
290
|
return db
|
|
291
291
|
raise RuntimeError(
|
|
292
|
-
"No database bound. Call orm_bind(db) or set
|
|
292
|
+
"No database bound. Call orm_bind(db) or set TINA4_DATABASE_URL in .env"
|
|
293
293
|
)
|
|
294
294
|
return _database
|
|
295
295
|
|
|
@@ -249,41 +249,79 @@ def _resolve_extends(scss: str, placeholders: dict) -> str:
|
|
|
249
249
|
|
|
250
250
|
|
|
251
251
|
def _eval_math(scss: str) -> str:
|
|
252
|
-
"""Evaluate simple math in property values (e.g., 10px + 5px).
|
|
252
|
+
"""Evaluate simple math in property values (e.g., 10px + 5px).
|
|
253
|
+
|
|
254
|
+
Mixed-unit arithmetic is left verbatim — that is exactly what CSS calc()
|
|
255
|
+
is for, and folding it silently produces invalid output (see #42). Math
|
|
256
|
+
inside calc(...) is preserved untouched on the same principle: the author
|
|
257
|
+
asked the browser to compute it.
|
|
258
|
+
|
|
259
|
+
Rules for folding:
|
|
260
|
+
* Both operands unitless → fold
|
|
261
|
+
* Same unit on both operands → fold, keep unit
|
|
262
|
+
* One operand unitless for * or / → fold, keep the other unit
|
|
263
|
+
* Anything else (mixed units on +/-, etc) → leave verbatim
|
|
264
|
+
"""
|
|
265
|
+
# Step 1 — mask calc(...) ranges so the math regex cannot eat into them.
|
|
266
|
+
# CSS calc() is the contract for mixed-unit arithmetic; the browser does it.
|
|
267
|
+
placeholders: list[str] = []
|
|
268
|
+
|
|
269
|
+
def _mask(m):
|
|
270
|
+
placeholders.append(m.group(0))
|
|
271
|
+
return f"\x00CALC{len(placeholders) - 1}\x00"
|
|
272
|
+
|
|
273
|
+
masked = re.sub(r'calc\([^()]*\)', _mask, scss)
|
|
274
|
+
|
|
275
|
+
# Step 2 — run the math fold on what remains.
|
|
253
276
|
def _calc(m):
|
|
254
277
|
full = m.group(0)
|
|
255
278
|
try:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
unit_match = re.search(r'[a-z%]+', m.group(1))
|
|
262
|
-
unit = unit_match.group() if unit_match else ""
|
|
263
|
-
|
|
264
|
-
if op == "+":
|
|
265
|
-
result = num1 + num2
|
|
266
|
-
elif op == "-":
|
|
267
|
-
result = num1 - num2
|
|
268
|
-
elif op == "*":
|
|
269
|
-
result = num1 * num2
|
|
270
|
-
elif op == "/":
|
|
271
|
-
result = num1 / num2 if num2 != 0 else 0
|
|
272
|
-
else:
|
|
273
|
-
return full
|
|
274
|
-
|
|
275
|
-
# Format result
|
|
276
|
-
if result == int(result):
|
|
277
|
-
return f"{int(result)}{unit}"
|
|
278
|
-
return f"{result:.2f}{unit}"
|
|
279
|
+
num1 = float(m.group(1))
|
|
280
|
+
unit1 = m.group(2) or ""
|
|
281
|
+
op = m.group(3)
|
|
282
|
+
num2 = float(m.group(4))
|
|
283
|
+
unit2 = m.group(5) or ""
|
|
279
284
|
except (ValueError, AttributeError):
|
|
280
285
|
return full
|
|
281
286
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
287
|
+
# Decide result unit; bail (leave verbatim) if units are incompatible.
|
|
288
|
+
if unit1 == unit2:
|
|
289
|
+
unit = unit1
|
|
290
|
+
elif op in ("*", "/") and not unit1:
|
|
291
|
+
unit = unit2
|
|
292
|
+
elif op in ("*", "/") and not unit2:
|
|
293
|
+
unit = unit1
|
|
294
|
+
else:
|
|
295
|
+
return full
|
|
296
|
+
|
|
297
|
+
if op == "+":
|
|
298
|
+
result = num1 + num2
|
|
299
|
+
elif op == "-":
|
|
300
|
+
result = num1 - num2
|
|
301
|
+
elif op == "*":
|
|
302
|
+
result = num1 * num2
|
|
303
|
+
elif op == "/":
|
|
304
|
+
if num2 == 0:
|
|
305
|
+
return full
|
|
306
|
+
result = num1 / num2
|
|
307
|
+
else:
|
|
308
|
+
return full
|
|
309
|
+
|
|
310
|
+
if result == int(result):
|
|
311
|
+
return f"{int(result)}{unit}"
|
|
312
|
+
return f"{result:.2f}{unit}"
|
|
313
|
+
|
|
314
|
+
folded = re.sub(
|
|
315
|
+
r'([\d.]+)([a-z%]*)\s*([+\-*/])\s*([\d.]+)([a-z%]*)',
|
|
316
|
+
_calc, masked
|
|
285
317
|
)
|
|
286
318
|
|
|
319
|
+
# Step 3 — restore the calc() ranges verbatim.
|
|
320
|
+
def _unmask(m):
|
|
321
|
+
return placeholders[int(m.group(1))]
|
|
322
|
+
|
|
323
|
+
return re.sub(r'\x00CALC(\d+)\x00', _unmask, folded)
|
|
324
|
+
|
|
287
325
|
|
|
288
326
|
def _resolve_color_functions(scss: str, variables: dict) -> str:
|
|
289
327
|
"""Resolve lighten(), darken(), rgba() functions."""
|
|
@@ -177,6 +177,72 @@ class ServiceContext:
|
|
|
177
177
|
self.log = _get_log()
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
# Service base class (3.13.1) — class-based pattern with run/stop/should_stop
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class Service:
|
|
186
|
+
"""Base class for class-based background services managed by ServiceRunner.
|
|
187
|
+
|
|
188
|
+
Cross-framework parity with PHP ``Tina4\\Service``, Ruby ``Tina4::Service``
|
|
189
|
+
and Node ``Tina4Service``. Subclasses implement ``run()`` (the work
|
|
190
|
+
loop); the base provides ``stop()`` and ``should_stop()`` helpers
|
|
191
|
+
backed by an internal flag.
|
|
192
|
+
|
|
193
|
+
class EmailQueueWorker(Service):
|
|
194
|
+
def run(self):
|
|
195
|
+
while not self.should_stop():
|
|
196
|
+
job = queue.pop()
|
|
197
|
+
if job is None:
|
|
198
|
+
time.sleep(1); continue
|
|
199
|
+
process(job)
|
|
200
|
+
job.complete()
|
|
201
|
+
|
|
202
|
+
runner = ServiceRunner()
|
|
203
|
+
runner.register_service("emails", EmailQueueWorker())
|
|
204
|
+
runner.start()
|
|
205
|
+
|
|
206
|
+
Function-style services without state can still register directly via
|
|
207
|
+
``runner.register('name', callable, interval=N)``. The two patterns
|
|
208
|
+
coexist.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
def __init__(self):
|
|
212
|
+
self._running = True
|
|
213
|
+
|
|
214
|
+
def run(self) -> None:
|
|
215
|
+
"""Main work loop — subclasses MUST override."""
|
|
216
|
+
raise NotImplementedError(
|
|
217
|
+
f"{self.__class__.__name__}.run() must be implemented by the subclass"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def stop(self) -> None:
|
|
221
|
+
"""Signal this service to stop. Override for custom shutdown behaviour
|
|
222
|
+
but always call ``super().stop()`` so the internal flag gets set —
|
|
223
|
+
``should_stop()`` reads from it.
|
|
224
|
+
"""
|
|
225
|
+
self._running = False
|
|
226
|
+
|
|
227
|
+
def should_stop(self) -> bool:
|
|
228
|
+
"""Returns True once ``stop()`` has been called. Use inside ``run()``
|
|
229
|
+
loops as the exit condition::
|
|
230
|
+
|
|
231
|
+
def run(self):
|
|
232
|
+
while not self.should_stop():
|
|
233
|
+
# do work
|
|
234
|
+
"""
|
|
235
|
+
return not self._running
|
|
236
|
+
|
|
237
|
+
def __call__(self, *args, **kwargs):
|
|
238
|
+
"""Make the Service instance callable so it can be passed directly
|
|
239
|
+
to ``ServiceRunner.register('name', service_instance, daemon=True)``.
|
|
240
|
+
Cleanly dispatches to ``run()`` while ignoring the runner's
|
|
241
|
+
ServiceContext argument (Service subclasses manage their own state).
|
|
242
|
+
"""
|
|
243
|
+
return self.run()
|
|
244
|
+
|
|
245
|
+
|
|
180
246
|
# ---------------------------------------------------------------------------
|
|
181
247
|
# ServiceRunner
|
|
182
248
|
# ---------------------------------------------------------------------------
|
|
@@ -221,6 +287,43 @@ class ServiceRunner:
|
|
|
221
287
|
"started_at": None,
|
|
222
288
|
})
|
|
223
289
|
|
|
290
|
+
def register_service(self, name: str, service: "Service", max_retries: int = 3):
|
|
291
|
+
"""Register a class-based service (:class:`Service` instance) by name.
|
|
292
|
+
|
|
293
|
+
Wraps the Service's ``run()`` method as the handler callable that
|
|
294
|
+
``start()`` invokes. Defaults to ``daemon=True`` because Service
|
|
295
|
+
subclasses manage their own loop inside ``run()``.
|
|
296
|
+
|
|
297
|
+
class EmailWorker(Service):
|
|
298
|
+
def run(self):
|
|
299
|
+
while not self.should_stop():
|
|
300
|
+
process_one()
|
|
301
|
+
|
|
302
|
+
runner.register_service("emails", EmailWorker())
|
|
303
|
+
runner.start()
|
|
304
|
+
|
|
305
|
+
Cross-framework parity with PHP ``ServiceRunner::registerService``,
|
|
306
|
+
Ruby ``ServiceRunner.register_service``, Node
|
|
307
|
+
``ServiceRunner.registerService``.
|
|
308
|
+
"""
|
|
309
|
+
if not isinstance(service, Service):
|
|
310
|
+
raise TypeError(
|
|
311
|
+
f"service must be a Service instance, got {type(service).__name__}"
|
|
312
|
+
)
|
|
313
|
+
self.services.append({
|
|
314
|
+
"name": name,
|
|
315
|
+
"handler": service, # Service instances are callable (see __call__)
|
|
316
|
+
"interval": 0,
|
|
317
|
+
"cron": None,
|
|
318
|
+
"daemon": True, # Service subclasses manage their own loop
|
|
319
|
+
"max_retries": max_retries,
|
|
320
|
+
"retries": 0,
|
|
321
|
+
"running": False,
|
|
322
|
+
"last_run": None,
|
|
323
|
+
"started_at": None,
|
|
324
|
+
"instance": service, # keep reference so stop() can route to service.stop()
|
|
325
|
+
})
|
|
326
|
+
|
|
224
327
|
# -- Lifecycle ----------------------------------------------------------
|
|
225
328
|
|
|
226
329
|
def start(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.0 → tina4_python-3.13.2}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.0 → tina4_python-3.13.2}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|