tina4-python 3.13.1__tar.gz → 3.13.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tina4_python-3.13.1 → tina4_python-3.13.3}/PKG-INFO +1 -1
- {tina4_python-3.13.1 → tina4_python-3.13.3}/pyproject.toml +1 -1
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/__init__.py +4 -1
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/connection.py +3 -2
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/debug/__init__.py +51 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/dev_admin/__init__.py +8 -4
- tina4_python-3.13.3/tina4_python/env.py +111 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/orm/model.py +2 -2
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/__init__.py +65 -27
- {tina4_python-3.13.1 → tina4_python-3.13.3}/.gitignore +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/README.md +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
|
|
|
8
8
|
|
|
9
9
|
One import, everything works.
|
|
10
10
|
"""
|
|
11
|
-
__version__ = "3.13.
|
|
11
|
+
__version__ = "3.13.3"
|
|
12
12
|
|
|
13
13
|
# ── Route decorators ──
|
|
14
14
|
from tina4_python.core.router import ( # noqa: E402, F401
|
|
@@ -42,6 +42,9 @@ from tina4_python.orm import ( # noqa: E402, F401
|
|
|
42
42
|
has_many, has_one, belongs_to, # relationship descriptors
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
+
# ── Env (typed env-var helpers) ──
|
|
46
|
+
from tina4_python.env import Env # noqa: E402, F401
|
|
47
|
+
|
|
45
48
|
# ── Auth ──
|
|
46
49
|
from tina4_python.auth import Auth # noqa: E402, F401
|
|
47
50
|
|
|
@@ -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.
|
|
@@ -320,10 +320,57 @@ class Log:
|
|
|
320
320
|
now = datetime.now(timezone.utc)
|
|
321
321
|
return now.strftime("%Y-%m-%dT%H:%M:%S.") + f"{now.microsecond // 1000:03d}Z"
|
|
322
322
|
|
|
323
|
+
# Frame names that belong to Log itself — walk past them when looking
|
|
324
|
+
# for the real caller. Kept as a class attribute so subclasses can
|
|
325
|
+
# extend if they layer extra wrappers (and so tests can introspect).
|
|
326
|
+
_OWN_FRAMES = frozenset({
|
|
327
|
+
"_caller_name", "_format_line", "_format", "_log",
|
|
328
|
+
"debug", "info", "warning", "error", "critical",
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def _caller_name(cls) -> str | None:
|
|
333
|
+
"""Return the function name that called Log.{debug,info,warning,error}.
|
|
334
|
+
|
|
335
|
+
Active only when ``TINA4_LOG_FUNC=true`` — the lookup uses
|
|
336
|
+
``inspect.currentframe()`` which is ~5% overhead per log call.
|
|
337
|
+
Walks past Log's own frames (``_OWN_FRAMES``) to land on the
|
|
338
|
+
real caller, so the count is robust whether the test calls
|
|
339
|
+
``_format_line`` directly or goes through ``Log.info`` →
|
|
340
|
+
``_log`` → ``_format_line``. Returns ``None`` if the stack is
|
|
341
|
+
too shallow (e.g. log called from module import) or if anything
|
|
342
|
+
goes wrong — never raises. Parity feature #41 across all four
|
|
343
|
+
Tina4 frameworks.
|
|
344
|
+
"""
|
|
345
|
+
import os
|
|
346
|
+
if os.environ.get("TINA4_LOG_FUNC", "").strip().lower() not in (
|
|
347
|
+
"1", "true", "on", "yes", "y", "t"
|
|
348
|
+
):
|
|
349
|
+
return None
|
|
350
|
+
try:
|
|
351
|
+
import inspect
|
|
352
|
+
frame = inspect.currentframe()
|
|
353
|
+
# Walk past Log's own frames. Cap the walk at 16 to defend
|
|
354
|
+
# against any pathological recursion / wrapper stack.
|
|
355
|
+
for _ in range(16):
|
|
356
|
+
if frame is None:
|
|
357
|
+
return None
|
|
358
|
+
name = frame.f_code.co_name
|
|
359
|
+
if name not in cls._OWN_FRAMES:
|
|
360
|
+
# Filter out anonymous / module-level callers — they're noise.
|
|
361
|
+
if name in ("<module>", "<lambda>", "<genexpr>", "<listcomp>", "<setcomp>", "<dictcomp>"):
|
|
362
|
+
return None
|
|
363
|
+
return name
|
|
364
|
+
frame = frame.f_back
|
|
365
|
+
return None
|
|
366
|
+
except Exception:
|
|
367
|
+
return None
|
|
368
|
+
|
|
323
369
|
@classmethod
|
|
324
370
|
def _format_line(cls, level: str, message: str, **kwargs) -> str:
|
|
325
371
|
timestamp = cls._timestamp()
|
|
326
372
|
request_id = get_request_id()
|
|
373
|
+
caller = cls._caller_name()
|
|
327
374
|
|
|
328
375
|
# JSON format wins whenever the explicit env opt-in is set,
|
|
329
376
|
# regardless of production flag (so dev devs can ship JSON to
|
|
@@ -336,6 +383,8 @@ class Log:
|
|
|
336
383
|
}
|
|
337
384
|
if request_id:
|
|
338
385
|
entry["request_id"] = request_id
|
|
386
|
+
if caller:
|
|
387
|
+
entry["function"] = caller
|
|
339
388
|
if kwargs:
|
|
340
389
|
entry["context"] = {k: v for k, v in kwargs.items()}
|
|
341
390
|
return json.dumps(entry, default=str)
|
|
@@ -345,6 +394,8 @@ class Log:
|
|
|
345
394
|
parts = [timestamp, f"[{level_str}]"]
|
|
346
395
|
if request_id:
|
|
347
396
|
parts.append(f"[{request_id}]")
|
|
397
|
+
if caller:
|
|
398
|
+
parts.append(f"[{caller}]")
|
|
348
399
|
parts.append(message)
|
|
349
400
|
if kwargs:
|
|
350
401
|
parts.append(json.dumps(kwargs, default=str))
|
|
@@ -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)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Tina4 Env — Typed environment-variable helpers.
|
|
2
|
+
# Copyright 2007 - present Tina4
|
|
3
|
+
# License: MIT
|
|
4
|
+
"""Typed environment-variable helpers — zero-deps.
|
|
5
|
+
|
|
6
|
+
Reading env vars by hand gets old fast: every boolean flag becomes a
|
|
7
|
+
``os.getenv("TINA4_DEBUG", "false").lower() in ("1", "true", "on", "yes")``
|
|
8
|
+
incantation. Every numeric tuning knob needs a try/except around int().
|
|
9
|
+
Env centralises that. Same API across all four Tina4 frameworks
|
|
10
|
+
(``Tina4\\Env`` in PHP, ``Tina4::Env`` in Ruby, ``Env`` in Node.js).
|
|
11
|
+
|
|
12
|
+
from tina4_python import Env
|
|
13
|
+
|
|
14
|
+
debug = Env.bool("TINA4_DEBUG", default=False)
|
|
15
|
+
workers = Env.int("WORKERS", default=4)
|
|
16
|
+
rate = Env.float("RATE_LIMIT", default=10.0)
|
|
17
|
+
region = Env.str("AWS_REGION", default="us-east-1")
|
|
18
|
+
|
|
19
|
+
Values are accepted case-insensitively after ``strip()``. Truthy:
|
|
20
|
+
``1 / true / on / yes / y / t``. Falsy: ``0 / false / off / no / n / f``.
|
|
21
|
+
Any other value triggers the ``default``. Unparseable ints and floats
|
|
22
|
+
log a warning via ``Log`` and fall back to ``default`` — never raise.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import os
|
|
27
|
+
|
|
28
|
+
# Lazy import so ``import tina4_python.env`` doesn't drag the whole Log
|
|
29
|
+
# chain in for callers who don't want it (CLI scaffolding, etc).
|
|
30
|
+
_TRUTHY = frozenset({"1", "true", "on", "yes", "y", "t"})
|
|
31
|
+
_FALSY = frozenset({"0", "false", "off", "no", "n", "f", ""})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _log_warning(message: str) -> None:
|
|
35
|
+
"""Emit a warning via Log without creating a circular import at module load."""
|
|
36
|
+
try:
|
|
37
|
+
from tina4_python.debug import Log
|
|
38
|
+
Log.warning(message)
|
|
39
|
+
except Exception:
|
|
40
|
+
# Log not yet wired (e.g. very early bootstrap) — silently skip.
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Env:
|
|
45
|
+
"""Typed environment-variable helpers.
|
|
46
|
+
|
|
47
|
+
All methods are classmethods so callers can write ``Env.bool(...)``
|
|
48
|
+
without instantiating anything — matching the PHP/Ruby/Node ports.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def bool(cls, name: str, default: bool = False) -> bool:
|
|
53
|
+
"""Read ``name`` and coerce to bool.
|
|
54
|
+
|
|
55
|
+
Truthy values (case-insensitive after strip): ``1``, ``true``,
|
|
56
|
+
``on``, ``yes``, ``y``, ``t``. Falsy: ``0``, ``false``, ``off``,
|
|
57
|
+
``no``, ``n``, ``f``, empty string. Anything else returns the
|
|
58
|
+
``default`` — never raises.
|
|
59
|
+
"""
|
|
60
|
+
raw = os.environ.get(name)
|
|
61
|
+
if raw is None:
|
|
62
|
+
return default
|
|
63
|
+
token = raw.strip().lower()
|
|
64
|
+
if token in _TRUTHY:
|
|
65
|
+
return True
|
|
66
|
+
if token in _FALSY:
|
|
67
|
+
return False
|
|
68
|
+
return default
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def int(cls, name: str, default: int = 0) -> int:
|
|
72
|
+
"""Read ``name`` and coerce to int. Returns ``default`` on parse failure."""
|
|
73
|
+
raw = os.environ.get(name)
|
|
74
|
+
if raw is None:
|
|
75
|
+
return default
|
|
76
|
+
try:
|
|
77
|
+
return int(raw.strip())
|
|
78
|
+
except (ValueError, TypeError):
|
|
79
|
+
_log_warning(
|
|
80
|
+
f"Env.int({name!r}): could not parse {raw!r} as int — "
|
|
81
|
+
f"using default {default!r}"
|
|
82
|
+
)
|
|
83
|
+
return default
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def float(cls, name: str, default: float = 0.0) -> float:
|
|
87
|
+
"""Read ``name`` and coerce to float. Returns ``default`` on parse failure."""
|
|
88
|
+
raw = os.environ.get(name)
|
|
89
|
+
if raw is None:
|
|
90
|
+
return default
|
|
91
|
+
try:
|
|
92
|
+
return float(raw.strip())
|
|
93
|
+
except (ValueError, TypeError):
|
|
94
|
+
_log_warning(
|
|
95
|
+
f"Env.float({name!r}): could not parse {raw!r} as float — "
|
|
96
|
+
f"using default {default!r}"
|
|
97
|
+
)
|
|
98
|
+
return default
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def str(cls, name: str, default: str = "") -> str:
|
|
102
|
+
"""Read ``name`` as a string. Returns ``default`` if unset.
|
|
103
|
+
|
|
104
|
+
Whitespace is preserved — this is a pass-through for the raw env
|
|
105
|
+
value. ``Env.str("PATH")`` is exactly ``os.environ.get("PATH", "")``
|
|
106
|
+
with a more discoverable name.
|
|
107
|
+
"""
|
|
108
|
+
raw = os.environ.get(name)
|
|
109
|
+
if raw is None:
|
|
110
|
+
return default
|
|
111
|
+
return raw
|
|
@@ -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."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.1 → tina4_python-3.13.3}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.1 → tina4_python-3.13.3}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|