tina4-python 3.13.46__tar.gz → 3.13.47__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.46 → tina4_python-3.13.47}/PKG-INFO +1 -1
- {tina4_python-3.13.46 → tina4_python-3.13.47}/pyproject.toml +1 -1
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/__init__.py +1 -1
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/router.py +7 -8
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/server.py +37 -6
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/migration/runner.py +126 -48
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/__init__.py +24 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/.gitignore +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/README.md +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/env.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -219,18 +219,17 @@ class Router:
|
|
|
219
219
|
"""Register a global middleware class applied to every route.
|
|
220
220
|
|
|
221
221
|
Equivalent to decorating every handler with @middleware(middleware_class).
|
|
222
|
+
Delegates to the single ``Middleware`` global registry that the request
|
|
223
|
+
dispatcher actually consults — mirrors PHP ``Router::use`` ->
|
|
224
|
+
``Middleware::use``, Ruby ``Router.use`` -> ``Middleware.use`` and the
|
|
225
|
+
Node equivalent. (Before #55 this wrote to a private ``Router._global_middleware``
|
|
226
|
+
list that nothing read, so globals registered via ``Router.use`` never ran.)
|
|
222
227
|
|
|
223
228
|
Args:
|
|
224
229
|
middleware_class: A class with before_*/after_* static methods.
|
|
225
230
|
"""
|
|
226
|
-
from tina4_python.core import
|
|
227
|
-
|
|
228
|
-
_mw_module.register_global(middleware_class)
|
|
229
|
-
else:
|
|
230
|
-
# Fallback: store in a module-level list the dispatcher can pick up.
|
|
231
|
-
if not hasattr(cls, "_global_middleware"):
|
|
232
|
-
cls._global_middleware = []
|
|
233
|
-
cls._global_middleware.append(middleware_class)
|
|
231
|
+
from tina4_python.core.middleware import Middleware # avoid circular import
|
|
232
|
+
Middleware.use(middleware_class)
|
|
234
233
|
|
|
235
234
|
@classmethod
|
|
236
235
|
def get(cls, path: str, handler, middleware: list = None, swagger_meta: dict = None, template: str = None, **options) -> "RouteRef":
|
|
@@ -127,10 +127,19 @@ def _auto_discover(root_dir: str = "src"):
|
|
|
127
127
|
importlib.import_module(module_name)
|
|
128
128
|
_discovered_mtimes[module_name] = current_mtime
|
|
129
129
|
Log.debug(f"Loaded: {module_name}")
|
|
130
|
-
elif
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
130
|
+
elif module_name not in _discovered_mtimes:
|
|
131
|
+
# Already in sys.modules but NEVER recorded by discovery — it was
|
|
132
|
+
# imported TRANSITIVELY (e.g. `from src.x import y` pulled it in
|
|
133
|
+
# before the walk reached its file). Record its current mtime as
|
|
134
|
+
# the baseline WITHOUT re-importing. Re-importing here would
|
|
135
|
+
# del+re-add a fresh module object, while the earlier importer
|
|
136
|
+
# keeps the STALE one — module-level singletons would silently
|
|
137
|
+
# diverge (issue #53). We only ever reload a module WE loaded.
|
|
138
|
+
_discovered_mtimes[module_name] = current_mtime
|
|
139
|
+
elif current_mtime > _discovered_mtimes[module_name]:
|
|
140
|
+
# Changed since WE loaded it — re-execute so edits to an existing
|
|
141
|
+
# route take effect in-process. Scope guard: only ever evict a
|
|
142
|
+
# module that lives under our discovery package. Deleting a
|
|
134
143
|
# tina4_python.* / third-party module would break shared
|
|
135
144
|
# singletons and class identity — never do that here.
|
|
136
145
|
if module_name == root_pkg or module_name.startswith(root_pkg + "."):
|
|
@@ -1322,6 +1331,28 @@ def _middleware_500(response: Response, mw_inst, method_name: str, error: Except
|
|
|
1322
1331
|
})
|
|
1323
1332
|
|
|
1324
1333
|
|
|
1334
|
+
def _effective_middleware(route: dict) -> list:
|
|
1335
|
+
"""Resolve the middleware that actually runs for a route.
|
|
1336
|
+
|
|
1337
|
+
Global middleware (registered via ``Middleware.use`` / ``Router.use``) runs
|
|
1338
|
+
on EVERY route, BEFORE the route's own middleware, in registration order.
|
|
1339
|
+
The list is deduped (by class) so a class that is both global and attached
|
|
1340
|
+
to the route runs once. This is the fix for issue #55 — globals were
|
|
1341
|
+
registered into ``Middleware._global_middleware`` but the dispatcher only
|
|
1342
|
+
ever iterated ``route["middleware"]``, so global middleware never ran.
|
|
1343
|
+
Mirrors the PHP/Ruby/Node dispatchers, which already fold globals in.
|
|
1344
|
+
"""
|
|
1345
|
+
from tina4_python.core.middleware import Middleware
|
|
1346
|
+
resolved = []
|
|
1347
|
+
seen = set()
|
|
1348
|
+
for mw in list(Middleware.get_global()) + list(route.get("middleware", [])):
|
|
1349
|
+
key = mw if isinstance(mw, type) else type(mw)
|
|
1350
|
+
if key not in seen:
|
|
1351
|
+
seen.add(key)
|
|
1352
|
+
resolved.append(mw)
|
|
1353
|
+
return resolved
|
|
1354
|
+
|
|
1355
|
+
|
|
1325
1356
|
def _run_before_middleware(request: Request, response: Response, route: dict) -> tuple[Request, Response, bool]:
|
|
1326
1357
|
"""Run class-based before_* middleware methods. Returns (request, response, skip_handler).
|
|
1327
1358
|
|
|
@@ -1338,7 +1369,7 @@ def _run_before_middleware(request: Request, response: Response, route: dict) ->
|
|
|
1338
1369
|
before_* that sets status >= 400 also short-circuits the handler.
|
|
1339
1370
|
"""
|
|
1340
1371
|
skip = False
|
|
1341
|
-
for _mw_cls in route
|
|
1372
|
+
for _mw_cls in _effective_middleware(route):
|
|
1342
1373
|
if _is_function_middleware(_mw_cls):
|
|
1343
1374
|
continue # Handled by the continuation wrapper instead
|
|
1344
1375
|
_mw_inst = _mw_cls() if isinstance(_mw_cls, type) else _mw_cls
|
|
@@ -1378,7 +1409,7 @@ def _run_after_middleware(request: Request, response: Response, route: dict) ->
|
|
|
1378
1409
|
Resilience (M2): each after_* call is wrapped — a method that THROWS is
|
|
1379
1410
|
logged and converted to a clean 500; remaining after_* still run.
|
|
1380
1411
|
"""
|
|
1381
|
-
for _mw_cls in route
|
|
1412
|
+
for _mw_cls in _effective_middleware(route):
|
|
1382
1413
|
if _is_function_middleware(_mw_cls):
|
|
1383
1414
|
continue
|
|
1384
1415
|
_mw_inst = _mw_cls() if isinstance(_mw_cls, type) else _mw_cls
|
|
@@ -332,59 +332,137 @@ def _normalize_quotes(sql: str) -> str:
|
|
|
332
332
|
|
|
333
333
|
|
|
334
334
|
def _split_statements(sql: str, delimiter: str = ";") -> list[str]:
|
|
335
|
-
"""Split SQL into individual statements
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
335
|
+
"""Split SQL into individual statements with a single-pass, quote- and
|
|
336
|
+
comment-aware scanner.
|
|
337
|
+
|
|
338
|
+
The split decision is made character by character so the delimiter only ever
|
|
339
|
+
fires in real statement position. This is the fix for issue #54: the old
|
|
340
|
+
implementation split on ``delimiter`` BEFORE stripping ``-- …`` line comments,
|
|
341
|
+
so a ``;`` *inside* a line comment fragmented one statement into several
|
|
342
|
+
broken pieces. A scanner that knows where it is — code, comment, or string —
|
|
343
|
+
cannot make that mistake.
|
|
344
|
+
|
|
345
|
+
Handled, in priority order, only when NOT already inside a stored-proc block:
|
|
346
|
+
|
|
347
|
+
- **Stored-procedure blocks** delimited by ``$$`` or ``//`` are kept intact
|
|
348
|
+
(their inner ``;`` never splits). A ``//`` preceded by ``:`` is a URL
|
|
349
|
+
scheme (``https://…``), not a block delimiter, so it is left alone.
|
|
350
|
+
- **Block comments** ``/* … */`` are stripped.
|
|
351
|
+
- **Line comments** ``-- …`` are stripped to end of line (the newline is
|
|
352
|
+
kept, so line structure survives). A ``;`` here never splits.
|
|
353
|
+
- **String literals** — single-quoted ``'…'`` and double-quoted identifiers
|
|
354
|
+
``"…"`` — are copied verbatim, honouring the SQL doubled-quote escape
|
|
355
|
+
(``''`` / ``""``). A ``;``, ``--`` or ``/*`` inside a literal is data, not
|
|
356
|
+
a delimiter or comment, so it is preserved untouched.
|
|
357
|
+
- The **delimiter** ends a statement only when reached outside all of the
|
|
358
|
+
above. Empty statements are dropped; each kept statement is trimmed.
|
|
359
|
+
|
|
360
|
+
Smart/curly quotes are normalized to straight ASCII first. Mirrors the
|
|
361
|
+
tina4-php ``Migration::splitStatements`` scanner for cross-framework parity.
|
|
345
362
|
"""
|
|
346
363
|
# Normalize smart/curly quotes to straight ASCII first, so SQL pasted from
|
|
347
364
|
# an editor/doc (which converts " → “ ” and ' → ‘ ’) actually runs.
|
|
348
365
|
sql = _normalize_quotes(sql)
|
|
349
366
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if
|
|
387
|
-
|
|
367
|
+
statements: list[str] = []
|
|
368
|
+
current: list[str] = []
|
|
369
|
+
n = len(sql)
|
|
370
|
+
dlen = len(delimiter)
|
|
371
|
+
i = 0
|
|
372
|
+
in_dollar_block = False
|
|
373
|
+
in_slash_block = False
|
|
374
|
+
|
|
375
|
+
while i < n:
|
|
376
|
+
ch = sql[i]
|
|
377
|
+
|
|
378
|
+
# $$ … $$ stored-proc block (toggle in/out).
|
|
379
|
+
if not in_slash_block and ch == "$" and i + 1 < n and sql[i + 1] == "$":
|
|
380
|
+
current.append("$$")
|
|
381
|
+
i += 2
|
|
382
|
+
in_dollar_block = not in_dollar_block
|
|
383
|
+
continue
|
|
384
|
+
|
|
385
|
+
# // … // stored-proc block (toggle). The `//` must NOT be preceded by a
|
|
386
|
+
# colon, so a URL scheme (`https://…`) or any `://` literal is never
|
|
387
|
+
# treated as a block delimiter (it would otherwise swallow everything
|
|
388
|
+
# between two `//` occurrences and skip statement splitting).
|
|
389
|
+
if (not in_dollar_block and ch == "/" and i + 1 < n and sql[i + 1] == "/"
|
|
390
|
+
and not (i > 0 and sql[i - 1] == ":")):
|
|
391
|
+
current.append("//")
|
|
392
|
+
i += 2
|
|
393
|
+
in_slash_block = not in_slash_block
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
# Inside a stored-proc block: consume verbatim (inner ; never splits).
|
|
397
|
+
if in_dollar_block or in_slash_block:
|
|
398
|
+
current.append(ch)
|
|
399
|
+
i += 1
|
|
400
|
+
continue
|
|
401
|
+
|
|
402
|
+
# Block comment /* … */ — stripped.
|
|
403
|
+
if ch == "/" and i + 1 < n and sql[i + 1] == "*":
|
|
404
|
+
end = sql.find("*/", i + 2)
|
|
405
|
+
i = (end + 2) if end != -1 else n
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
# Line comment -- … — stripped to end of line; the newline is left for
|
|
409
|
+
# the next iteration so line structure (and statement boundaries on the
|
|
410
|
+
# NEXT line) survive. A ';' inside the comment is NOT a delimiter.
|
|
411
|
+
if ch == "-" and i + 1 < n and sql[i + 1] == "-":
|
|
412
|
+
end = sql.find("\n", i + 2)
|
|
413
|
+
i = end if end != -1 else n
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
# Single-quoted string literal — '' escapes a quote. Copied verbatim so a
|
|
417
|
+
# ';' / '--' / '/*' inside the value is data, not a delimiter/comment.
|
|
418
|
+
if ch == "'":
|
|
419
|
+
current.append("'")
|
|
420
|
+
i += 1
|
|
421
|
+
while i < n:
|
|
422
|
+
if sql[i] == "'" and i + 1 < n and sql[i + 1] == "'":
|
|
423
|
+
current.append("''")
|
|
424
|
+
i += 2
|
|
425
|
+
elif sql[i] == "'":
|
|
426
|
+
current.append("'")
|
|
427
|
+
i += 1
|
|
428
|
+
break
|
|
429
|
+
else:
|
|
430
|
+
current.append(sql[i])
|
|
431
|
+
i += 1
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
# Double-quoted identifier — "" escapes a quote. Same verbatim handling.
|
|
435
|
+
if ch == '"':
|
|
436
|
+
current.append('"')
|
|
437
|
+
i += 1
|
|
438
|
+
while i < n:
|
|
439
|
+
if sql[i] == '"' and i + 1 < n and sql[i + 1] == '"':
|
|
440
|
+
current.append('""')
|
|
441
|
+
i += 2
|
|
442
|
+
elif sql[i] == '"':
|
|
443
|
+
current.append('"')
|
|
444
|
+
i += 1
|
|
445
|
+
break
|
|
446
|
+
else:
|
|
447
|
+
current.append(sql[i])
|
|
448
|
+
i += 1
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
# Statement delimiter — only reached outside blocks/comments/strings.
|
|
452
|
+
if delimiter and sql.startswith(delimiter, i):
|
|
453
|
+
stmt = "".join(current).strip()
|
|
454
|
+
if stmt:
|
|
455
|
+
statements.append(stmt)
|
|
456
|
+
current = []
|
|
457
|
+
i += dlen
|
|
458
|
+
continue
|
|
459
|
+
|
|
460
|
+
current.append(ch)
|
|
461
|
+
i += 1
|
|
462
|
+
|
|
463
|
+
stmt = "".join(current).strip()
|
|
464
|
+
if stmt:
|
|
465
|
+
statements.append(stmt)
|
|
388
466
|
return statements
|
|
389
467
|
|
|
390
468
|
|
|
@@ -131,6 +131,9 @@ def _compile(scss: str) -> str:
|
|
|
131
131
|
# 6. Resolve @extend
|
|
132
132
|
scss = _resolve_extends(scss, placeholders)
|
|
133
133
|
|
|
134
|
+
# 6.5. Resolve #{ ... } interpolation (before $var substitution + nesting).
|
|
135
|
+
scss = _resolve_interpolation(scss, variables)
|
|
136
|
+
|
|
134
137
|
# 7. Substitute variables
|
|
135
138
|
scss = _substitute_variables(scss, variables)
|
|
136
139
|
|
|
@@ -171,6 +174,27 @@ def _substitute_variables(scss: str, variables: dict) -> str:
|
|
|
171
174
|
return scss
|
|
172
175
|
|
|
173
176
|
|
|
177
|
+
def _resolve_interpolation(scss: str, variables: dict) -> str:
|
|
178
|
+
"""Resolve SCSS ``#{ ... }`` interpolation.
|
|
179
|
+
|
|
180
|
+
Each ``#{ expr }`` is replaced by its resolved inner text: a ``$variable``
|
|
181
|
+
inside the braces resolves to its value, anything else is inlined verbatim
|
|
182
|
+
(trimmed). This lets a value carry a variable inside a string context the
|
|
183
|
+
plain ``$var`` substitution can't reach — e.g. ``calc(100% - #{$gap})`` ->
|
|
184
|
+
``calc(100% - 20px)`` — and lets a variable appear in a selector
|
|
185
|
+
(``.icon-#{$name}`` -> ``.icon-home``). Run BEFORE nested-rule flattening so
|
|
186
|
+
the literal ``{``/``}`` never confuse the block matcher. The inner cannot
|
|
187
|
+
contain braces (interpolation is a leaf expression), so ``[^{}]*`` is safe.
|
|
188
|
+
"""
|
|
189
|
+
def _resolve(m):
|
|
190
|
+
inner = m.group(1).strip()
|
|
191
|
+
for name in sorted(variables.keys(), key=len, reverse=True):
|
|
192
|
+
inner = inner.replace(f"${name}", variables[name])
|
|
193
|
+
return inner
|
|
194
|
+
|
|
195
|
+
return re.sub(r'#\{([^{}]*)\}', _resolve, scss)
|
|
196
|
+
|
|
197
|
+
|
|
174
198
|
def _extract_mixins(scss: str, mixins: dict) -> str:
|
|
175
199
|
"""Extract @mixin definitions."""
|
|
176
200
|
pattern = re.compile(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.46 → tina4_python-3.13.47}/tina4_python/public/images/tina4-logo-icon.webp
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
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/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
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/queue_backends/rabbitmq_backend.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
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.46 → tina4_python-3.13.47}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|