tina4-python 3.13.53__tar.gz → 3.13.55__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.53 → tina4_python-3.13.55}/PKG-INFO +1 -1
- {tina4_python-3.13.53 → tina4_python-3.13.55}/pyproject.toml +1 -1
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/migration/runner.py +107 -47
- {tina4_python-3.13.53 → tina4_python-3.13.55}/.gitignore +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/README.md +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/Testing.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/events.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/request.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/response.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/router.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/core/server.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/docs.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/docstore/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/env.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/test/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -87,7 +87,7 @@ def _create_v3_table(db) -> None:
|
|
|
87
87
|
db.execute("""
|
|
88
88
|
CREATE TABLE tina4_migration (
|
|
89
89
|
id INTEGER NOT NULL PRIMARY KEY,
|
|
90
|
-
|
|
90
|
+
migration_name VARCHAR(500) NOT NULL UNIQUE,
|
|
91
91
|
description VARCHAR(500),
|
|
92
92
|
batch INTEGER DEFAULT 1 NOT NULL,
|
|
93
93
|
executed_at VARCHAR(50) NOT NULL,
|
|
@@ -98,7 +98,7 @@ def _create_v3_table(db) -> None:
|
|
|
98
98
|
# Engine-aware bookkeeping DDL. Each engine spells an auto-increment
|
|
99
99
|
# integer PK differently (SQLite AUTOINCREMENT, PostgreSQL SERIAL, MySQL
|
|
100
100
|
# AUTO_INCREMENT, MSSQL IDENTITY), and a TEXT column cannot carry a UNIQUE
|
|
101
|
-
# constraint on MySQL -- so
|
|
101
|
+
# constraint on MySQL -- so migration_name is VARCHAR. (SQLite gives VARCHAR
|
|
102
102
|
# TEXT affinity, so this stays behaviour-identical there.) Mirrors the
|
|
103
103
|
# engine-aware DDL in ORM.create_table; without it `migrate()` died with
|
|
104
104
|
# "syntax error at AUTOINCREMENT" on PostgreSQL/MySQL/MSSQL.
|
|
@@ -111,7 +111,7 @@ def _create_v3_table(db) -> None:
|
|
|
111
111
|
db.execute(f"""
|
|
112
112
|
CREATE TABLE tina4_migration (
|
|
113
113
|
{id_column},
|
|
114
|
-
|
|
114
|
+
migration_name VARCHAR(500) NOT NULL UNIQUE,
|
|
115
115
|
description VARCHAR(500),
|
|
116
116
|
batch INTEGER NOT NULL DEFAULT 1,
|
|
117
117
|
executed_at VARCHAR(50) NOT NULL,
|
|
@@ -136,7 +136,7 @@ def _build_stem_map(migration_folder: str | None) -> list[str]:
|
|
|
136
136
|
"""Scan the migrations folder and return a list of file stems.
|
|
137
137
|
|
|
138
138
|
Used by the v2→v3 backfill to match a v2 row's `description` to the
|
|
139
|
-
actual file on disk, so the resulting `
|
|
139
|
+
actual file on disk, so the resulting `migration_name` matches what
|
|
140
140
|
`migrate()` will compare against next.
|
|
141
141
|
"""
|
|
142
142
|
if not migration_folder:
|
|
@@ -153,7 +153,7 @@ def _build_stem_map(migration_folder: str | None) -> list[str]:
|
|
|
153
153
|
return stems
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
def
|
|
156
|
+
def _resolve_migration_name(description: str, stems: list[str]) -> str:
|
|
157
157
|
"""Find the file stem that corresponds to a v2 description, or fall back.
|
|
158
158
|
|
|
159
159
|
Match strategy, in order:
|
|
@@ -179,11 +179,11 @@ def _resolve_migration_id(description: str, stems: list[str]) -> str:
|
|
|
179
179
|
def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
|
|
180
180
|
"""In-place upgrade of a Python-v2 tina4_migration table to v3.
|
|
181
181
|
|
|
182
|
-
1. ALTER TABLE ADD the v3 columns (
|
|
182
|
+
1. ALTER TABLE ADD the v3 columns (migration_name, batch, executed_at)
|
|
183
183
|
alongside the existing v2 columns. v2 columns stay in place so a
|
|
184
184
|
manual rollback path remains open; they are simply ignored from
|
|
185
185
|
now on.
|
|
186
|
-
2. Backfill every v2 row's
|
|
186
|
+
2. Backfill every v2 row's migration_name by matching `description`
|
|
187
187
|
against the on-disk file stems. Falls back to the description
|
|
188
188
|
verbatim when no file matches. All legacy rows get batch=1 and
|
|
189
189
|
executed_at='legacy'.
|
|
@@ -192,15 +192,25 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
|
|
|
192
192
|
"Detected legacy v2 tina4_migration schema — performing in-place upgrade to v3"
|
|
193
193
|
)
|
|
194
194
|
|
|
195
|
+
# A v2 table has no id generator; on Firebird, ids for rows recorded after
|
|
196
|
+
# the upgrade come from GEN_TINA4_MIGRATION_ID (see _record_migration), so
|
|
197
|
+
# make sure it exists. Fresh v3 tables get it in _create_v3_table.
|
|
198
|
+
if _is_firebird(db):
|
|
199
|
+
try:
|
|
200
|
+
db.execute("CREATE GENERATOR GEN_TINA4_MIGRATION_ID")
|
|
201
|
+
db.commit()
|
|
202
|
+
except Exception:
|
|
203
|
+
pass # Generator may already exist
|
|
204
|
+
|
|
195
205
|
cols = _column_names(db)
|
|
196
206
|
|
|
197
|
-
if "
|
|
207
|
+
if "migration_name" not in cols:
|
|
198
208
|
col_type = "VARCHAR(500)" if _is_firebird(db) else "TEXT"
|
|
199
209
|
try:
|
|
200
|
-
db.execute(f"ALTER TABLE tina4_migration ADD
|
|
210
|
+
db.execute(f"ALTER TABLE tina4_migration ADD migration_name {col_type}")
|
|
201
211
|
db.commit()
|
|
202
212
|
except Exception as e:
|
|
203
|
-
logger.debug(f"v2→v3 upgrade: ALTER ADD
|
|
213
|
+
logger.debug(f"v2→v3 upgrade: ALTER ADD migration_name skipped: {e}")
|
|
204
214
|
|
|
205
215
|
if "batch" not in cols:
|
|
206
216
|
try:
|
|
@@ -224,7 +234,7 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
|
|
|
224
234
|
|
|
225
235
|
try:
|
|
226
236
|
rows = db.fetch(
|
|
227
|
-
"SELECT description, passed FROM tina4_migration WHERE
|
|
237
|
+
"SELECT description, passed FROM tina4_migration WHERE migration_name IS NULL",
|
|
228
238
|
limit=100000,
|
|
229
239
|
).records
|
|
230
240
|
except Exception as e:
|
|
@@ -239,13 +249,13 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
|
|
|
239
249
|
if not desc:
|
|
240
250
|
continue
|
|
241
251
|
|
|
242
|
-
|
|
252
|
+
migration_name = _resolve_migration_name(desc, stems)
|
|
243
253
|
|
|
244
254
|
try:
|
|
245
255
|
db.execute(
|
|
246
|
-
"UPDATE tina4_migration SET
|
|
247
|
-
"WHERE description = ? AND
|
|
248
|
-
[
|
|
256
|
+
"UPDATE tina4_migration SET migration_name = ?, batch = 1 "
|
|
257
|
+
"WHERE description = ? AND migration_name IS NULL",
|
|
258
|
+
[migration_name, desc],
|
|
249
259
|
)
|
|
250
260
|
db.commit()
|
|
251
261
|
backfilled += 1
|
|
@@ -267,8 +277,8 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
|
|
|
267
277
|
def _ensure_tracking_table(db, migration_folder: str | None = None):
|
|
268
278
|
"""Create or upgrade the migration tracking table.
|
|
269
279
|
|
|
270
|
-
Handles v2→v3 upgrade: v2 tables have `description` but no `
|
|
271
|
-
When detected, adds the missing v3 columns and backfills `
|
|
280
|
+
Handles v2→v3 upgrade: v2 tables have `description` but no `migration_name`.
|
|
281
|
+
When detected, adds the missing v3 columns and backfills `migration_name`
|
|
272
282
|
by matching `description` against the on-disk file stems so subsequent
|
|
273
283
|
`migrate()` calls do not re-apply already-applied migrations.
|
|
274
284
|
|
|
@@ -279,8 +289,26 @@ def _ensure_tracking_table(db, migration_folder: str | None = None):
|
|
|
279
289
|
return
|
|
280
290
|
|
|
281
291
|
cols = _column_names(db)
|
|
292
|
+
|
|
293
|
+
# In-place rename for a table created by an older v3 (<= 3.13.54), which
|
|
294
|
+
# named the column `migration_id`. The canonical name is now
|
|
295
|
+
# `migration_name` (unified across all four frameworks). Add the new column
|
|
296
|
+
# and copy the values across so already-applied migrations are still seen as
|
|
297
|
+
# applied — without this the `migration_name`-based read below finds nothing
|
|
298
|
+
# and every migration re-runs. The old `migration_id` column is left in
|
|
299
|
+
# place (harmless, ignored from now on).
|
|
300
|
+
if "migration_id" in cols and "migration_name" not in cols:
|
|
301
|
+
col_type = "VARCHAR(500)" if _is_firebird(db) else "TEXT"
|
|
302
|
+
try:
|
|
303
|
+
db.execute(f"ALTER TABLE tina4_migration ADD migration_name {col_type}")
|
|
304
|
+
db.execute("UPDATE tina4_migration SET migration_name = migration_id")
|
|
305
|
+
db.commit()
|
|
306
|
+
cols = _column_names(db)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.debug(f"v3 migration_id->migration_name rename skipped: {e}")
|
|
309
|
+
|
|
282
310
|
has_v3_shape = (
|
|
283
|
-
"
|
|
311
|
+
"migration_name" in cols
|
|
284
312
|
and "batch" in cols
|
|
285
313
|
and "executed_at" in cols
|
|
286
314
|
)
|
|
@@ -292,12 +320,12 @@ def _get_executed(db) -> set[str]:
|
|
|
292
320
|
"""Get set of already-executed migration IDs."""
|
|
293
321
|
try:
|
|
294
322
|
result = db.fetch(
|
|
295
|
-
"SELECT
|
|
323
|
+
"SELECT migration_name FROM tina4_migration WHERE passed = 1",
|
|
296
324
|
limit=10000,
|
|
297
325
|
)
|
|
298
|
-
return {row["
|
|
326
|
+
return {row["migration_name"] for row in result.records if row.get("migration_name")}
|
|
299
327
|
except Exception:
|
|
300
|
-
# Fallback for v2 tables where
|
|
328
|
+
# Fallback for v2 tables where migration_name may not exist yet
|
|
301
329
|
result = db.fetch(
|
|
302
330
|
"SELECT description FROM tina4_migration WHERE passed = 1",
|
|
303
331
|
limit=10000,
|
|
@@ -331,6 +359,27 @@ def _normalize_quotes(sql: str) -> str:
|
|
|
331
359
|
return _SMART_QUOTE_RE.sub(lambda m: _SMART_QUOTES[m.group(0)], sql)
|
|
332
360
|
|
|
333
361
|
|
|
362
|
+
_SET_TERM_RE = re.compile(r"^SET\s+TERM\s+(\S+)$", re.IGNORECASE)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _parse_set_term(statement: str) -> str | None:
|
|
366
|
+
"""Return the new terminator from a ``SET TERM <new> <current>`` directive.
|
|
367
|
+
|
|
368
|
+
``SET TERM`` is a script-level directive (recognised by isql and other
|
|
369
|
+
InterBase/Firebird tooling, not run by the engine) that changes the
|
|
370
|
+
terminator separating statements. Recognising it lets a statement whose own
|
|
371
|
+
body contains the default ``;`` terminator — a trigger, stored procedure or
|
|
372
|
+
``EXECUTE BLOCK`` — be kept intact rather than split on those inner ``;``.
|
|
373
|
+
The terminator may be more than one character (e.g. ``!!``).
|
|
374
|
+
|
|
375
|
+
:param statement: A single, already-stripped statement.
|
|
376
|
+
:returns: The new terminator, or ``None`` when *statement* is not a
|
|
377
|
+
``SET TERM`` directive.
|
|
378
|
+
"""
|
|
379
|
+
match = _SET_TERM_RE.match(statement.strip())
|
|
380
|
+
return match.group(1) if match else None
|
|
381
|
+
|
|
382
|
+
|
|
334
383
|
def _split_statements(sql: str, delimiter: str = ";") -> list[str]:
|
|
335
384
|
"""Split SQL into individual statements with a single-pass, quote- and
|
|
336
385
|
comment-aware scanner.
|
|
@@ -356,6 +405,10 @@ def _split_statements(sql: str, delimiter: str = ";") -> list[str]:
|
|
|
356
405
|
a delimiter or comment, so it is preserved untouched.
|
|
357
406
|
- The **delimiter** ends a statement only when reached outside all of the
|
|
358
407
|
above. Empty statements are dropped; each kept statement is trimmed.
|
|
408
|
+
- A **SET TERM directive** (``SET TERM <new> <current>``) switches the active
|
|
409
|
+
terminator and is consumed, never emitted, so a statement whose own body
|
|
410
|
+
contains the default terminator survives as one. Multi-character
|
|
411
|
+
terminators (e.g. ``!!``) are supported.
|
|
359
412
|
|
|
360
413
|
Smart/curly quotes are normalized to straight ASCII first. Mirrors the
|
|
361
414
|
tina4-php ``Migration::splitStatements`` scanner for cross-framework parity.
|
|
@@ -367,7 +420,6 @@ def _split_statements(sql: str, delimiter: str = ";") -> list[str]:
|
|
|
367
420
|
statements: list[str] = []
|
|
368
421
|
current: list[str] = []
|
|
369
422
|
n = len(sql)
|
|
370
|
-
dlen = len(delimiter)
|
|
371
423
|
i = 0
|
|
372
424
|
in_dollar_block = False
|
|
373
425
|
in_slash_block = False
|
|
@@ -448,20 +500,28 @@ def _split_statements(sql: str, delimiter: str = ";") -> list[str]:
|
|
|
448
500
|
i += 1
|
|
449
501
|
continue
|
|
450
502
|
|
|
451
|
-
# Statement delimiter — only reached outside blocks/comments/strings.
|
|
503
|
+
# Statement delimiter — only reached outside blocks/comments/strings. A
|
|
504
|
+
# SET TERM directive switches the active terminator and is consumed
|
|
505
|
+
# (never emitted); any other completed statement is collected.
|
|
452
506
|
if delimiter and sql.startswith(delimiter, i):
|
|
507
|
+
i += len(delimiter)
|
|
453
508
|
stmt = "".join(current).strip()
|
|
454
|
-
if stmt:
|
|
455
|
-
statements.append(stmt)
|
|
456
509
|
current = []
|
|
457
|
-
|
|
510
|
+
if stmt:
|
|
511
|
+
new_term = _parse_set_term(stmt)
|
|
512
|
+
if new_term is not None:
|
|
513
|
+
delimiter = new_term
|
|
514
|
+
else:
|
|
515
|
+
statements.append(stmt)
|
|
458
516
|
continue
|
|
459
517
|
|
|
460
518
|
current.append(ch)
|
|
461
519
|
i += 1
|
|
462
520
|
|
|
521
|
+
# Trailing statement (may not end with a delimiter). A trailing SET TERM
|
|
522
|
+
# directive is a no-op — consume it, don't emit it.
|
|
463
523
|
stmt = "".join(current).strip()
|
|
464
|
-
if stmt:
|
|
524
|
+
if stmt and _parse_set_term(stmt) is None:
|
|
465
525
|
statements.append(stmt)
|
|
466
526
|
return statements
|
|
467
527
|
|
|
@@ -605,9 +665,9 @@ def _migrate(db, migration_folder: str = "migrations", delimiter: str = ";") ->
|
|
|
605
665
|
)
|
|
606
666
|
|
|
607
667
|
for mig_file in migration_files:
|
|
608
|
-
|
|
668
|
+
migration_name = mig_file.stem # e.g., "000001_create_users_table"
|
|
609
669
|
|
|
610
|
-
if
|
|
670
|
+
if migration_name in executed:
|
|
611
671
|
continue
|
|
612
672
|
|
|
613
673
|
try:
|
|
@@ -630,18 +690,18 @@ def _migrate(db, migration_folder: str = "migrations", delimiter: str = ";") ->
|
|
|
630
690
|
|
|
631
691
|
# Record as passed
|
|
632
692
|
now = datetime.now(timezone.utc).isoformat()
|
|
633
|
-
desc = re.sub(r"^\d+_", "",
|
|
693
|
+
desc = re.sub(r"^\d+_", "", migration_name, count=1).replace("_", " ")
|
|
634
694
|
if _is_firebird(db):
|
|
635
695
|
row = db.fetch_one("SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS next_id FROM RDB$DATABASE")
|
|
636
696
|
next_id = row["next_id"] if row else 1
|
|
637
697
|
db.execute(
|
|
638
|
-
"INSERT INTO tina4_migration (id,
|
|
639
|
-
[next_id,
|
|
698
|
+
"INSERT INTO tina4_migration (id, migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?, 1)",
|
|
699
|
+
[next_id, migration_name, desc, batch, now],
|
|
640
700
|
)
|
|
641
701
|
else:
|
|
642
702
|
db.execute(
|
|
643
|
-
"INSERT INTO tina4_migration (
|
|
644
|
-
[
|
|
703
|
+
"INSERT INTO tina4_migration (migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, 1)",
|
|
704
|
+
[migration_name, desc, batch, now],
|
|
645
705
|
)
|
|
646
706
|
db.commit()
|
|
647
707
|
ran.append(mig_file.name)
|
|
@@ -671,7 +731,7 @@ def _rollback(db, migration_folder: str = "migrations", delimiter: str = ";") ->
|
|
|
671
731
|
|
|
672
732
|
last_batch = row["max_batch"]
|
|
673
733
|
result = db.fetch(
|
|
674
|
-
"SELECT
|
|
734
|
+
"SELECT migration_name FROM tina4_migration WHERE batch = ? AND passed = 1 ORDER BY migration_name DESC",
|
|
675
735
|
[last_batch],
|
|
676
736
|
limit=10000,
|
|
677
737
|
)
|
|
@@ -680,7 +740,7 @@ def _rollback(db, migration_folder: str = "migrations", delimiter: str = ";") ->
|
|
|
680
740
|
rolled_back = []
|
|
681
741
|
|
|
682
742
|
for migration in result.records:
|
|
683
|
-
mid = migration["
|
|
743
|
+
mid = migration["migration_name"]
|
|
684
744
|
py_file = folder / f"{mid}.py"
|
|
685
745
|
down_file = folder / f"{mid}.down.sql"
|
|
686
746
|
|
|
@@ -703,7 +763,7 @@ def _rollback(db, migration_folder: str = "migrations", delimiter: str = ";") ->
|
|
|
703
763
|
)
|
|
704
764
|
|
|
705
765
|
db.execute(
|
|
706
|
-
"DELETE FROM tina4_migration WHERE
|
|
766
|
+
"DELETE FROM tina4_migration WHERE migration_name = ?",
|
|
707
767
|
[mid],
|
|
708
768
|
)
|
|
709
769
|
db.commit()
|
|
@@ -858,12 +918,12 @@ class Migration:
|
|
|
858
918
|
row = self._db.fetch_one("SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS next_id FROM RDB$DATABASE")
|
|
859
919
|
next_id = row["next_id"] if row else 1
|
|
860
920
|
self._db.execute(
|
|
861
|
-
"INSERT INTO tina4_migration (id,
|
|
921
|
+
"INSERT INTO tina4_migration (id, migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?, ?)",
|
|
862
922
|
[next_id, name, desc, batch, now, passed],
|
|
863
923
|
)
|
|
864
924
|
else:
|
|
865
925
|
self._db.execute(
|
|
866
|
-
"INSERT INTO tina4_migration (
|
|
926
|
+
"INSERT INTO tina4_migration (migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?)",
|
|
867
927
|
[name, desc, batch, now, passed],
|
|
868
928
|
)
|
|
869
929
|
|
|
@@ -874,7 +934,7 @@ class Migration:
|
|
|
874
934
|
name: Migration ID to remove (stem of the filename).
|
|
875
935
|
"""
|
|
876
936
|
self._db.execute(
|
|
877
|
-
"DELETE FROM tina4_migration WHERE
|
|
937
|
+
"DELETE FROM tina4_migration WHERE migration_name = ?",
|
|
878
938
|
[name],
|
|
879
939
|
)
|
|
880
940
|
|
|
@@ -883,7 +943,7 @@ def _status(db, migration_folder: str = "migrations") -> dict:
|
|
|
883
943
|
"""Get migration status (internal implementation).
|
|
884
944
|
|
|
885
945
|
Returns {"completed": [...], "pending": [...]}, where each entry is
|
|
886
|
-
a dict with '
|
|
946
|
+
a dict with 'migration_name', 'description', and (for completed)
|
|
887
947
|
'executed_at' and 'batch'.
|
|
888
948
|
"""
|
|
889
949
|
_ensure_tracking_table(db, migration_folder)
|
|
@@ -902,12 +962,12 @@ def _status(db, migration_folder: str = "migrations") -> dict:
|
|
|
902
962
|
|
|
903
963
|
# Build completed list from the database with execution metadata
|
|
904
964
|
result = db.fetch(
|
|
905
|
-
"SELECT
|
|
965
|
+
"SELECT migration_name, description, batch, executed_at FROM tina4_migration WHERE passed = 1 ORDER BY migration_name",
|
|
906
966
|
limit=10000,
|
|
907
967
|
)
|
|
908
968
|
completed = [
|
|
909
969
|
{
|
|
910
|
-
"
|
|
970
|
+
"migration_name": row["migration_name"],
|
|
911
971
|
"description": row["description"],
|
|
912
972
|
"batch": row["batch"],
|
|
913
973
|
"executed_at": row["executed_at"],
|
|
@@ -918,11 +978,11 @@ def _status(db, migration_folder: str = "migrations") -> dict:
|
|
|
918
978
|
# Build pending list from files not yet executed
|
|
919
979
|
pending = []
|
|
920
980
|
for sql_file in sql_files:
|
|
921
|
-
|
|
922
|
-
if
|
|
923
|
-
desc = re.sub(r"^\d+_", "",
|
|
981
|
+
migration_name = sql_file.stem
|
|
982
|
+
if migration_name not in executed:
|
|
983
|
+
desc = re.sub(r"^\d+_", "", migration_name, count=1).replace("_", " ")
|
|
924
984
|
pending.append({
|
|
925
|
-
"
|
|
985
|
+
"migration_name": migration_name,
|
|
926
986
|
"description": desc,
|
|
927
987
|
})
|
|
928
988
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
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.53 → tina4_python-3.13.55}/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.53 → tina4_python-3.13.55}/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.53 → tina4_python-3.13.55}/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
|
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/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.53 → tina4_python-3.13.55}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.13.53 → tina4_python-3.13.55}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|