tina4-python 3.13.54__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.
Files changed (158) hide show
  1. {tina4_python-3.13.54 → tina4_python-3.13.55}/PKG-INFO +1 -1
  2. {tina4_python-3.13.54 → tina4_python-3.13.55}/pyproject.toml +1 -1
  3. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/migration/runner.py +59 -41
  4. {tina4_python-3.13.54 → tina4_python-3.13.55}/.gitignore +0 -0
  5. {tina4_python-3.13.54 → tina4_python-3.13.55}/README.md +0 -0
  6. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/CLAUDE.md +0 -0
  7. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/HtmlElement.py +0 -0
  8. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/Testing.py +0 -0
  9. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/__init__.py +0 -0
  10. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/rate_limiter.py +0 -0
  22. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/core/server.py +0 -0
  26. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/crud/__init__.py +0 -0
  27. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/__init__.py +0 -0
  28. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/adapter.py +0 -0
  29. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/connection.py +0 -0
  30. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/firebird.py +0 -0
  31. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/mongodb.py +0 -0
  32. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/mssql.py +0 -0
  33. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/mysql.py +0 -0
  34. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/odbc.py +0 -0
  35. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/postgres.py +0 -0
  36. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/database/sqlite.py +0 -0
  37. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/debug/__init__.py +0 -0
  38. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/debug/error_overlay.py +0 -0
  39. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/dev_admin/__init__.py +0 -0
  40. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/dev_admin/metrics.py +0 -0
  41. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/dev_admin/plan.py +0 -0
  42. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/dev_admin/project_index.py +0 -0
  43. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/docs.py +0 -0
  44. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/docstore/__init__.py +0 -0
  45. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/dotenv/__init__.py +0 -0
  46. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/env.py +0 -0
  47. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/frond/FROND.md +0 -0
  48. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/frond/__init__.py +0 -0
  49. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/frond/engine.py +0 -0
  50. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/auth/meta.json +0 -0
  51. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  52. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/database/meta.json +0 -0
  53. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  54. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/error-overlay/meta.json +0 -0
  55. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  56. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/orm/meta.json +0 -0
  57. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  58. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  59. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/queue/meta.json +0 -0
  60. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  61. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/rest-api/meta.json +0 -0
  62. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  63. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/templates/meta.json +0 -0
  64. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  65. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  66. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/graphql/__init__.py +0 -0
  67. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/i18n/__init__.py +0 -0
  68. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/mcp/__init__.py +0 -0
  69. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/mcp/protocol.py +0 -0
  70. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/mcp/tools.py +0 -0
  71. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/messenger/__init__.py +0 -0
  72. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/migration/__init__.py +0 -0
  73. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/orm/__init__.py +0 -0
  74. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/orm/fields.py +0 -0
  75. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/orm/model.py +0 -0
  76. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/__feedback/widget.js +0 -0
  77. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/css/tina4.css +0 -0
  78. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/css/tina4.min.css +0 -0
  79. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/favicon.ico +0 -0
  80. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/images/logo.svg +0 -0
  81. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  82. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/js/frond.js +0 -0
  83. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/js/frond.min.js +0 -0
  84. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  85. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  86. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/js/tina4.min.js +0 -0
  87. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/js/tina4js.min.js +0 -0
  88. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/swagger/index.html +0 -0
  89. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  90. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/query_builder/__init__.py +0 -0
  91. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue/__init__.py +0 -0
  92. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue/job.py +0 -0
  93. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue/kafka_backend.py +0 -0
  94. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue/lite_backend.py +0 -0
  95. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue/mongo_backend.py +0 -0
  96. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue/rabbitmq_backend.py +0 -0
  97. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue_backends/__init__.py +0 -0
  98. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue_backends/kafka_backend.py +0 -0
  99. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue_backends/mongo_backend.py +0 -0
  100. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  101. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/__init__.py +0 -0
  102. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  103. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_badges.scss +0 -0
  104. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  105. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_cards.scss +0 -0
  106. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_forms.scss +0 -0
  107. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_grid.scss +0 -0
  108. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_modals.scss +0 -0
  109. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_nav.scss +0 -0
  110. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_reset.scss +0 -0
  111. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_tables.scss +0 -0
  112. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_typography.scss +0 -0
  113. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  114. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/_variables.scss +0 -0
  115. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/base.scss +0 -0
  116. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/colors.scss +0 -0
  117. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/scss/tina4css/tina4.scss +0 -0
  118. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/seeder/__init__.py +0 -0
  119. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/service/__init__.py +0 -0
  120. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/session/__init__.py +0 -0
  121. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/session_handlers/__init__.py +0 -0
  122. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  123. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/session_handlers/redis_handler.py +0 -0
  124. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/session_handlers/valkey_handler.py +0 -0
  125. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/swagger/__init__.py +0 -0
  126. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/components/crud.twig +0 -0
  127. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  128. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  129. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/docker/python/Dockerfile +0 -0
  130. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  131. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/302.twig +0 -0
  132. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/401.twig +0 -0
  133. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/403.twig +0 -0
  134. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/404.twig +0 -0
  135. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/500.twig +0 -0
  136. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/502.twig +0 -0
  137. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/503.twig +0 -0
  138. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/errors/base.twig +0 -0
  139. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/frontend/README.md +0 -0
  140. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/templates/readme.md +0 -0
  141. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/test/__init__.py +0 -0
  142. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/test_client/__init__.py +0 -0
  143. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  144. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  145. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  146. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  147. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  148. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  149. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  150. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  151. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  152. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  153. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  154. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  155. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/validator/__init__.py +0 -0
  156. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/websocket/__init__.py +0 -0
  157. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/websocket/backplane.py +0 -0
  158. {tina4_python-3.13.54 → tina4_python-3.13.55}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.13.54
3
+ Version: 3.13.55
4
4
  Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.54"
3
+ version = "3.13.55"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -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
- migration_id VARCHAR(500) NOT NULL UNIQUE,
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 migration_id is VARCHAR. (SQLite gives VARCHAR
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
- migration_id VARCHAR(500) NOT NULL UNIQUE,
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 `migration_id` matches what
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 _resolve_migration_id(description: str, stems: list[str]) -> str:
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 (migration_id, batch, executed_at)
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 migration_id by matching `description`
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'.
@@ -204,13 +204,13 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
204
204
 
205
205
  cols = _column_names(db)
206
206
 
207
- if "migration_id" not in cols:
207
+ if "migration_name" not in cols:
208
208
  col_type = "VARCHAR(500)" if _is_firebird(db) else "TEXT"
209
209
  try:
210
- db.execute(f"ALTER TABLE tina4_migration ADD migration_id {col_type}")
210
+ db.execute(f"ALTER TABLE tina4_migration ADD migration_name {col_type}")
211
211
  db.commit()
212
212
  except Exception as e:
213
- logger.debug(f"v2→v3 upgrade: ALTER ADD migration_id skipped: {e}")
213
+ logger.debug(f"v2→v3 upgrade: ALTER ADD migration_name skipped: {e}")
214
214
 
215
215
  if "batch" not in cols:
216
216
  try:
@@ -234,7 +234,7 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
234
234
 
235
235
  try:
236
236
  rows = db.fetch(
237
- "SELECT description, passed FROM tina4_migration WHERE migration_id IS NULL",
237
+ "SELECT description, passed FROM tina4_migration WHERE migration_name IS NULL",
238
238
  limit=100000,
239
239
  ).records
240
240
  except Exception as e:
@@ -249,13 +249,13 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
249
249
  if not desc:
250
250
  continue
251
251
 
252
- migration_id = _resolve_migration_id(desc, stems)
252
+ migration_name = _resolve_migration_name(desc, stems)
253
253
 
254
254
  try:
255
255
  db.execute(
256
- "UPDATE tina4_migration SET migration_id = ?, batch = 1 "
257
- "WHERE description = ? AND migration_id IS NULL",
258
- [migration_id, desc],
256
+ "UPDATE tina4_migration SET migration_name = ?, batch = 1 "
257
+ "WHERE description = ? AND migration_name IS NULL",
258
+ [migration_name, desc],
259
259
  )
260
260
  db.commit()
261
261
  backfilled += 1
@@ -277,8 +277,8 @@ def _upgrade_v2_to_v3(db, migration_folder: str | None) -> None:
277
277
  def _ensure_tracking_table(db, migration_folder: str | None = None):
278
278
  """Create or upgrade the migration tracking table.
279
279
 
280
- Handles v2→v3 upgrade: v2 tables have `description` but no `migration_id`.
281
- When detected, adds the missing v3 columns and backfills `migration_id`
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`
282
282
  by matching `description` against the on-disk file stems so subsequent
283
283
  `migrate()` calls do not re-apply already-applied migrations.
284
284
 
@@ -289,8 +289,26 @@ def _ensure_tracking_table(db, migration_folder: str | None = None):
289
289
  return
290
290
 
291
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
+
292
310
  has_v3_shape = (
293
- "migration_id" in cols
311
+ "migration_name" in cols
294
312
  and "batch" in cols
295
313
  and "executed_at" in cols
296
314
  )
@@ -302,12 +320,12 @@ def _get_executed(db) -> set[str]:
302
320
  """Get set of already-executed migration IDs."""
303
321
  try:
304
322
  result = db.fetch(
305
- "SELECT migration_id FROM tina4_migration WHERE passed = 1",
323
+ "SELECT migration_name FROM tina4_migration WHERE passed = 1",
306
324
  limit=10000,
307
325
  )
308
- return {row["migration_id"] for row in result.records if row.get("migration_id")}
326
+ return {row["migration_name"] for row in result.records if row.get("migration_name")}
309
327
  except Exception:
310
- # Fallback for v2 tables where migration_id may not exist yet
328
+ # Fallback for v2 tables where migration_name may not exist yet
311
329
  result = db.fetch(
312
330
  "SELECT description FROM tina4_migration WHERE passed = 1",
313
331
  limit=10000,
@@ -647,9 +665,9 @@ def _migrate(db, migration_folder: str = "migrations", delimiter: str = ";") ->
647
665
  )
648
666
 
649
667
  for mig_file in migration_files:
650
- migration_id = mig_file.stem # e.g., "000001_create_users_table"
668
+ migration_name = mig_file.stem # e.g., "000001_create_users_table"
651
669
 
652
- if migration_id in executed:
670
+ if migration_name in executed:
653
671
  continue
654
672
 
655
673
  try:
@@ -672,18 +690,18 @@ def _migrate(db, migration_folder: str = "migrations", delimiter: str = ";") ->
672
690
 
673
691
  # Record as passed
674
692
  now = datetime.now(timezone.utc).isoformat()
675
- desc = re.sub(r"^\d+_", "", migration_id, count=1).replace("_", " ")
693
+ desc = re.sub(r"^\d+_", "", migration_name, count=1).replace("_", " ")
676
694
  if _is_firebird(db):
677
695
  row = db.fetch_one("SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS next_id FROM RDB$DATABASE")
678
696
  next_id = row["next_id"] if row else 1
679
697
  db.execute(
680
- "INSERT INTO tina4_migration (id, migration_id, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?, 1)",
681
- [next_id, migration_id, desc, batch, now],
698
+ "INSERT INTO tina4_migration (id, migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?, 1)",
699
+ [next_id, migration_name, desc, batch, now],
682
700
  )
683
701
  else:
684
702
  db.execute(
685
- "INSERT INTO tina4_migration (migration_id, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, 1)",
686
- [migration_id, desc, batch, now],
703
+ "INSERT INTO tina4_migration (migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, 1)",
704
+ [migration_name, desc, batch, now],
687
705
  )
688
706
  db.commit()
689
707
  ran.append(mig_file.name)
@@ -713,7 +731,7 @@ def _rollback(db, migration_folder: str = "migrations", delimiter: str = ";") ->
713
731
 
714
732
  last_batch = row["max_batch"]
715
733
  result = db.fetch(
716
- "SELECT migration_id FROM tina4_migration WHERE batch = ? AND passed = 1 ORDER BY migration_id DESC",
734
+ "SELECT migration_name FROM tina4_migration WHERE batch = ? AND passed = 1 ORDER BY migration_name DESC",
717
735
  [last_batch],
718
736
  limit=10000,
719
737
  )
@@ -722,7 +740,7 @@ def _rollback(db, migration_folder: str = "migrations", delimiter: str = ";") ->
722
740
  rolled_back = []
723
741
 
724
742
  for migration in result.records:
725
- mid = migration["migration_id"]
743
+ mid = migration["migration_name"]
726
744
  py_file = folder / f"{mid}.py"
727
745
  down_file = folder / f"{mid}.down.sql"
728
746
 
@@ -745,7 +763,7 @@ def _rollback(db, migration_folder: str = "migrations", delimiter: str = ";") ->
745
763
  )
746
764
 
747
765
  db.execute(
748
- "DELETE FROM tina4_migration WHERE migration_id = ?",
766
+ "DELETE FROM tina4_migration WHERE migration_name = ?",
749
767
  [mid],
750
768
  )
751
769
  db.commit()
@@ -900,12 +918,12 @@ class Migration:
900
918
  row = self._db.fetch_one("SELECT GEN_ID(GEN_TINA4_MIGRATION_ID, 1) AS next_id FROM RDB$DATABASE")
901
919
  next_id = row["next_id"] if row else 1
902
920
  self._db.execute(
903
- "INSERT INTO tina4_migration (id, migration_id, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?, ?)",
921
+ "INSERT INTO tina4_migration (id, migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?, ?)",
904
922
  [next_id, name, desc, batch, now, passed],
905
923
  )
906
924
  else:
907
925
  self._db.execute(
908
- "INSERT INTO tina4_migration (migration_id, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?)",
926
+ "INSERT INTO tina4_migration (migration_name, description, batch, executed_at, passed) VALUES (?, ?, ?, ?, ?)",
909
927
  [name, desc, batch, now, passed],
910
928
  )
911
929
 
@@ -916,7 +934,7 @@ class Migration:
916
934
  name: Migration ID to remove (stem of the filename).
917
935
  """
918
936
  self._db.execute(
919
- "DELETE FROM tina4_migration WHERE migration_id = ?",
937
+ "DELETE FROM tina4_migration WHERE migration_name = ?",
920
938
  [name],
921
939
  )
922
940
 
@@ -925,7 +943,7 @@ def _status(db, migration_folder: str = "migrations") -> dict:
925
943
  """Get migration status (internal implementation).
926
944
 
927
945
  Returns {"completed": [...], "pending": [...]}, where each entry is
928
- a dict with 'migration_id', 'description', and (for completed)
946
+ a dict with 'migration_name', 'description', and (for completed)
929
947
  'executed_at' and 'batch'.
930
948
  """
931
949
  _ensure_tracking_table(db, migration_folder)
@@ -944,12 +962,12 @@ def _status(db, migration_folder: str = "migrations") -> dict:
944
962
 
945
963
  # Build completed list from the database with execution metadata
946
964
  result = db.fetch(
947
- "SELECT migration_id, description, batch, executed_at FROM tina4_migration WHERE passed = 1 ORDER BY migration_id",
965
+ "SELECT migration_name, description, batch, executed_at FROM tina4_migration WHERE passed = 1 ORDER BY migration_name",
948
966
  limit=10000,
949
967
  )
950
968
  completed = [
951
969
  {
952
- "migration_id": row["migration_id"],
970
+ "migration_name": row["migration_name"],
953
971
  "description": row["description"],
954
972
  "batch": row["batch"],
955
973
  "executed_at": row["executed_at"],
@@ -960,11 +978,11 @@ def _status(db, migration_folder: str = "migrations") -> dict:
960
978
  # Build pending list from files not yet executed
961
979
  pending = []
962
980
  for sql_file in sql_files:
963
- migration_id = sql_file.stem
964
- if migration_id not in executed:
965
- desc = re.sub(r"^\d+_", "", migration_id, count=1).replace("_", " ")
981
+ migration_name = sql_file.stem
982
+ if migration_name not in executed:
983
+ desc = re.sub(r"^\d+_", "", migration_name, count=1).replace("_", " ")
966
984
  pending.append({
967
- "migration_id": migration_id,
985
+ "migration_name": migration_name,
968
986
  "description": desc,
969
987
  })
970
988
 
File without changes