tina4-python 3.13.60__tar.gz → 3.13.61__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 (166) hide show
  1. {tina4_python-3.13.60 → tina4_python-3.13.61}/PKG-INFO +1 -1
  2. {tina4_python-3.13.60 → tina4_python-3.13.61}/pyproject.toml +1 -1
  3. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/CLAUDE.md +5 -5
  4. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/__init__.py +1 -1
  5. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/orm/model.py +29 -1
  6. {tina4_python-3.13.60 → tina4_python-3.13.61}/.gitignore +0 -0
  7. {tina4_python-3.13.60 → tina4_python-3.13.61}/README.md +0 -0
  8. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/rate_limiter.py +0 -0
  22. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/core/server.py +0 -0
  26. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/crud/__init__.py +0 -0
  27. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/__init__.py +0 -0
  28. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/adapter.py +0 -0
  29. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/connection.py +0 -0
  30. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/firebird.py +0 -0
  31. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/mongodb.py +0 -0
  32. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/mssql.py +0 -0
  33. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/mysql.py +0 -0
  34. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/odbc.py +0 -0
  35. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/postgres.py +0 -0
  36. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/database/sqlite.py +0 -0
  37. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/debug/__init__.py +0 -0
  38. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/debug/error_overlay.py +0 -0
  39. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/dev_admin/__init__.py +0 -0
  40. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/dev_admin/metrics.py +0 -0
  41. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/dev_admin/plan.py +0 -0
  42. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/dev_admin/project_index.py +0 -0
  43. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/docs.py +0 -0
  44. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/docstore/__init__.py +0 -0
  45. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/dotenv/__init__.py +0 -0
  46. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/env.py +0 -0
  47. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/frond/FROND.md +0 -0
  48. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/frond/__init__.py +0 -0
  49. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/frond/engine.py +0 -0
  50. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/auth/meta.json +0 -0
  51. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  52. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/database/meta.json +0 -0
  53. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  54. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/error-overlay/meta.json +0 -0
  55. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  56. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/orm/meta.json +0 -0
  57. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  58. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  59. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/queue/meta.json +0 -0
  60. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  61. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/rest-api/meta.json +0 -0
  62. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  63. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/templates/meta.json +0 -0
  64. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  65. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  66. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/graphql/__init__.py +0 -0
  67. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/i18n/__init__.py +0 -0
  68. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/mcp/__init__.py +0 -0
  69. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/mcp/protocol.py +0 -0
  70. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/mcp/tools.py +0 -0
  71. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/messenger/__init__.py +0 -0
  72. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/migration/__init__.py +0 -0
  73. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/migration/runner.py +0 -0
  74. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/orm/__init__.py +0 -0
  75. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/orm/fields.py +0 -0
  76. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/__feedback/widget.js +0 -0
  77. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/css/tina4.css +0 -0
  78. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/css/tina4.min.css +0 -0
  79. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/favicon.ico +0 -0
  80. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/images/logo.svg +0 -0
  81. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  82. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/js/frond.js +0 -0
  83. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/js/frond.min.js +0 -0
  84. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  85. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  86. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/js/tina4.min.js +0 -0
  87. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/js/tina4js.min.js +0 -0
  88. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/swagger/index.html +0 -0
  89. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  90. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/query_builder/__init__.py +0 -0
  91. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue/__init__.py +0 -0
  92. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue/job.py +0 -0
  93. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue/kafka_backend.py +0 -0
  94. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue/lite_backend.py +0 -0
  95. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue/mongo_backend.py +0 -0
  96. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue/rabbitmq_backend.py +0 -0
  97. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue_backends/__init__.py +0 -0
  98. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue_backends/kafka_backend.py +0 -0
  99. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue_backends/mongo_backend.py +0 -0
  100. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  101. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/__init__.py +0 -0
  102. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/models/Attachment.py +0 -0
  103. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/models/Channel.py +0 -0
  104. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/models/ChannelMember.py +0 -0
  105. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/models/Message.py +0 -0
  106. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/models/Workspace.py +0 -0
  107. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/models/__init__.py +0 -0
  108. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/realtime/storage.py +0 -0
  109. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/__init__.py +0 -0
  110. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  111. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_badges.scss +0 -0
  112. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  113. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_cards.scss +0 -0
  114. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_forms.scss +0 -0
  115. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_grid.scss +0 -0
  116. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_modals.scss +0 -0
  117. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_nav.scss +0 -0
  118. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_reset.scss +0 -0
  119. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_tables.scss +0 -0
  120. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_typography.scss +0 -0
  121. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  122. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/_variables.scss +0 -0
  123. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/base.scss +0 -0
  124. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/colors.scss +0 -0
  125. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/scss/tina4css/tina4.scss +0 -0
  126. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/seeder/__init__.py +0 -0
  127. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/service/__init__.py +0 -0
  128. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/session/__init__.py +0 -0
  129. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/session_handlers/__init__.py +0 -0
  130. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  131. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/session_handlers/redis_handler.py +0 -0
  132. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/session_handlers/valkey_handler.py +0 -0
  133. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/swagger/__init__.py +0 -0
  134. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/components/crud.twig +0 -0
  135. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  136. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  137. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/docker/python/Dockerfile +0 -0
  138. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  139. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/302.twig +0 -0
  140. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/401.twig +0 -0
  141. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/403.twig +0 -0
  142. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/404.twig +0 -0
  143. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/500.twig +0 -0
  144. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/502.twig +0 -0
  145. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/503.twig +0 -0
  146. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/errors/base.twig +0 -0
  147. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/frontend/README.md +0 -0
  148. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/templates/readme.md +0 -0
  149. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/test/__init__.py +0 -0
  150. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/test_client/__init__.py +0 -0
  151. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  152. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  153. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  154. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  155. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  156. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  157. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  158. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  159. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  160. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  161. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  162. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  163. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/validator/__init__.py +0 -0
  164. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/websocket/__init__.py +0 -0
  165. {tina4_python-3.13.60 → tina4_python-3.13.61}/tina4_python/websocket/backplane.py +0 -0
  166. {tina4_python-3.13.60 → tina4_python-3.13.61}/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.60
3
+ Version: 3.13.61
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.60"
3
+ version = "3.13.61"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -262,16 +262,16 @@ Queue(topic="tasks").push({"action": "send_email"})
262
262
  ### 11. Key tina4_python Gotchas
263
263
 
264
264
  1. **Database import**: Use `from tina4_python.database import Database` (NOT `from tina4_python import Database`)
265
- 2. **noauth/secured import**: Use `from tina4_python.core.router import noauth, secured` (there is NO `tina4_python.Decorators` module). **Never** import `noauth` from `tina4_python.swagger` that version only affects documentation, not actual auth.
265
+ 2. **noauth/secured import**: Use `from tina4_python.core.router import noauth, secured` (there is NO `tina4_python.Decorators` module). `tina4_python.swagger` does **not** export `noauth` (importing it raises `ImportError`). The real docs-only trap is **`@security()`** (from `tina4_python.swagger`): it sets OpenAPI metadata only and does NOT gate a route — use `@secured()`/`@noauth()` for actual auth.
266
266
  2b. **Decorator ordering**: Route decorators (`@get`, `@post`, etc.) must be the **innermost** (closest to the function). Swagger/meta decorators (`@description`, `@tags`, `@noauth`, `@secured`) go above. Correct: `@noauth()` → `@description(...)` → `@post(...)` → `def handler`. Wrong: `@post(...)` → `@description(...)` → `def handler` (will crash).
267
- 3. **Jinja2 template syntax** (common mistakes):
267
+ 3. **Frond template syntax** (Frond is Jinja2-*like*, not Jinja2 — note the differences):
268
268
  - **Ternary operator supported**: Both `{{ x ? 'a' : 'b' }}` and `{{ 'a' if x else 'b' }}` work
269
- - **elif not elseif**: Use `{% elif %}` NOT `{% elseif %}`
269
+ - **elif or elseif**: Frond accepts **both** `{% elif %}` and `{% elseif %}`
270
270
  - **Unescaped output**: Both `{{ var | safe }}` and `{{ var | raw }}` work for unescaped output
271
271
  - **format filter**: Use `{{ "%.2f" | format(value) }}` for number formatting
272
272
  - **e() filter has NO arguments**: Use `{{ var|e }}` NOT `{{ var|e('js') }}` — Jinja2's `|e` is HTML-only with no mode parameter (that's PHP Twig syntax)
273
273
  - **JS string escaping**: Use `{{ var|replace("'", "\\'") }}` to escape single quotes for inline JS onclick handlers
274
- - **No `|escape('js')` or `|e('js')`**: These will throw `escape() takes 1 positional argument but 2 were given`
274
+ - **`|e`/`|escape` are HTML-only**: a mode arg like `|e('js')` is **ignored** by Frond (not an error) for inline-JS escaping use `{{ var|replace("'", "\\'") }}`
275
275
  - **Ternary inline**: Use `{{ 's' if count != 1 else '' }}` NOT `{{ count != 1 ? 's' : '' }}`
276
276
  - **Default values**: Use `{{ var|default('fallback') }}` — works on undefined and None
277
277
  - **Chaining filters**: `{{ var|default('')|replace("'", "\\'") }}` — left to right
@@ -289,7 +289,7 @@ Queue(topic="tasks").push({"action": "send_email"})
289
289
  4. **fetch_one()**: Returns a plain dict (or None), NOT a DatabaseResult
290
290
  5. **Dict access**: All query results use dict access `row["column"]` not attribute access `row.column`
291
291
  6. **Connection strings**: v3 uses standard URL format: `driver://host:port/database` with separate `username` and `password` parameters. Example: `Database("firebird://localhost:3050//path/to/db", "SYSDBA", "masterkey")`. Environment variable: `TINA4_DATABASE_URL`.
292
- 7. **Running the app**: `uv run python app.py <port> <name>` port and name are CLI args handled by tina4_python
292
+ 7. **Running the app**: use the `tina4` CLI — `tina4 serve`. The framework **refuses to boot via a plain `python app.py`** (it must be launched by the CLI; `TINA4_OVERRIDE_CLIENT=true` only for embedding). Port/name are CLI args.
293
293
  8. **SCSS**: Files in `src/scss/` are auto-compiled to `src/public/css/` on startup
294
294
  12. **Background tasks**: Use `background(fn, interval)` from `tina4_python.core.server` — never use `threading.Thread` for periodic work. The `background()` function runs tasks cooperatively in the asyncio event loop with proper shutdown handling.
295
295
 
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.13.60"
11
+ __version__ = "3.13.61"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -437,7 +437,35 @@ class ORM(metaclass=ORMMeta):
437
437
  # which db.execute()/insert()/update() populate, falling back to
438
438
  # the exception text) on self.last_error so it survives, and log
439
439
  # it with model/table context. ──
440
- self.last_error = db.get_error() or str(e)
440
+ cause = db.get_error() or str(e)
441
+ # ── DX hint (v3.13.60): turn a bare driver error into an
442
+ # actionable fix for the two commonest ORM write footguns. Match
443
+ # the message case-insensitively (SQLite says "no such table" /
444
+ # "no such column: is_deleted" / "has no column named is_deleted";
445
+ # Postgres/MySQL say "does not exist" / "doesn't exist"). Any
446
+ # OTHER error keeps its raw cause untouched so we never mask an
447
+ # unrelated failure (e.g. a NOT NULL / duplicate-PK violation). ──
448
+ low = cause.lower()
449
+ if self.soft_delete and "is_deleted" in low and (
450
+ "no such column" in low or "has no column" in low
451
+ or "does not exist" in low or "doesn't exist" in low
452
+ or "unknown column" in low
453
+ ):
454
+ cause += (
455
+ " — soft_delete=True requires an is_deleted column; declare "
456
+ "it (is_deleted = IntegerField(default=0)) or add a migration"
457
+ )
458
+ elif "no such table" in low or (
459
+ ("does not exist" in low or "doesn't exist" in low)
460
+ # exclude a column-not-found (e.g. Postgres 'column "x" does not exist')
461
+ # so a genuine missing-column error never gets a spurious table hint
462
+ and "column" not in low
463
+ ):
464
+ cause += (
465
+ f" — table '{table}' does not exist; call "
466
+ f"{type(self).__name__}.create_table() or run a migration"
467
+ )
468
+ self.last_error = cause
441
469
  Log.error(
442
470
  f"{type(self).__name__}.save() failed for table "
443
471
  f"'{table}': {self.last_error}"
File without changes