tina4-python 3.13.94__tar.gz → 3.13.96__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 (184) hide show
  1. {tina4_python-3.13.94 → tina4_python-3.13.96}/.gitignore +12 -0
  2. {tina4_python-3.13.94 → tina4_python-3.13.96}/PKG-INFO +21 -2
  3. {tina4_python-3.13.94 → tina4_python-3.13.96}/README.md +1 -1
  4. tina4_python-3.13.96/pyproject.toml +125 -0
  5. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/CLAUDE.md +15 -6
  6. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/__init__.py +2 -1
  7. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/api/__init__.py +10 -7
  8. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/auth/__init__.py +239 -35
  9. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/cache/__init__.py +630 -163
  10. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/cli/__init__.py +5 -1
  11. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/middleware.py +269 -31
  12. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/rate_limiter.py +28 -2
  13. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/request.py +108 -6
  14. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/response.py +92 -3
  15. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/router.py +55 -11
  16. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/server.py +1054 -302
  17. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/crud/__init__.py +10 -11
  18. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/adapter.py +411 -147
  19. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/connection.py +180 -23
  20. tina4_python-3.13.96/tina4_python/database/database_url.py +365 -0
  21. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/firebird.py +163 -48
  22. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/mongodb.py +15 -3
  23. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/mssql.py +58 -49
  24. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/mysql.py +47 -49
  25. tina4_python-3.13.96/tina4_python/database/odbc.py +224 -0
  26. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/postgres.py +39 -48
  27. tina4_python-3.13.96/tina4_python/database/sql_translator.py +272 -0
  28. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/sqlite.py +19 -38
  29. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/debug/__init__.py +262 -45
  30. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/dev_admin/__init__.py +96 -49
  31. tina4_python-3.13.96/tina4_python/dev_admin/metrics.py +375 -0
  32. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/docstore/__init__.py +240 -49
  33. tina4_python-3.13.96/tina4_python/dotenv/__init__.py +288 -0
  34. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mcp/tools.py +12 -2
  35. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/messenger/__init__.py +158 -79
  36. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/migration/runner.py +29 -2
  37. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mqtt/__init__.py +5 -1
  38. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/orm/model.py +91 -18
  39. tina4_python-3.13.96/tina4_python/public/css/tina4.min.css +1 -0
  40. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/__init__.py +35 -28
  41. tina4_python-3.13.96/tina4_python/queue/amqp_url.py +73 -0
  42. tina4_python-3.13.96/tina4_python/queue/kafka_backend.py +210 -0
  43. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/lite_backend.py +23 -1
  44. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/mongo_backend.py +65 -6
  45. tina4_python-3.13.96/tina4_python/queue/rabbitmq_backend.py +221 -0
  46. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/kafka_backend.py +13 -7
  47. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/mongo_backend.py +20 -7
  48. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/rabbitmq_backend.py +31 -17
  49. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session/__init__.py +190 -16
  50. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/__init__.py +8 -2
  51. tina4_python-3.13.96/tina4_python/session_handlers/memcached_handler.py +155 -0
  52. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/mongodb_handler.py +81 -29
  53. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/redis_handler.py +4 -1
  54. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/session_handlers/valkey_handler.py +4 -1
  55. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/swagger/__init__.py +6 -3
  56. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/websocket/__init__.py +9 -3
  57. tina4_python-3.13.94/pyproject.toml +0 -73
  58. tina4_python-3.13.94/tina4_python/database/odbc.py +0 -177
  59. tina4_python-3.13.94/tina4_python/dev_admin/metrics.py +0 -867
  60. tina4_python-3.13.94/tina4_python/dev_admin/metrics_engine.py +0 -108
  61. tina4_python-3.13.94/tina4_python/dotenv/__init__.py +0 -163
  62. tina4_python-3.13.94/tina4_python/public/css/tina4.min.css +0 -1
  63. tina4_python-3.13.94/tina4_python/queue/kafka_backend.py +0 -151
  64. tina4_python-3.13.94/tina4_python/queue/rabbitmq_backend.py +0 -186
  65. tina4_python-3.13.94/tina4_python/scss/__init__.py +0 -694
  66. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/HtmlElement.py +0 -0
  67. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/Testing.py +0 -0
  68. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/ai/__init__.py +0 -0
  69. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/container/__init__.py +0 -0
  70. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/context/__init__.py +0 -0
  71. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/context/chunker.py +0 -0
  72. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/__init__.py +0 -0
  73. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/cache.py +0 -0
  74. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/constants.py +0 -0
  75. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/core/events.py +0 -0
  76. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/database/__init__.py +0 -0
  77. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/debug/error_overlay.py +0 -0
  78. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/dev_admin/plan.py +0 -0
  79. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/dev_admin/project_index.py +0 -0
  80. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/docs.py +0 -0
  81. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/env.py +0 -0
  82. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/FROND.md +0 -0
  83. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/__init__.py +0 -0
  84. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/compiler.py +0 -0
  85. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/engine.py +0 -0
  86. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/frond/parser.py +0 -0
  87. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/auth/meta.json +0 -0
  88. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  89. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/database/meta.json +0 -0
  90. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  91. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/error-overlay/meta.json +0 -0
  92. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  93. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/orm/meta.json +0 -0
  94. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  95. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  96. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/queue/meta.json +0 -0
  97. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  98. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/rest-api/meta.json +0 -0
  99. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  100. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/templates/meta.json +0 -0
  101. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  102. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  103. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/graphql/__init__.py +0 -0
  104. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/i18n/__init__.py +0 -0
  105. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mcp/__init__.py +0 -0
  106. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mcp/protocol.py +0 -0
  107. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/migration/__init__.py +0 -0
  108. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/mqtt/message.py +0 -0
  109. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/orm/__init__.py +0 -0
  110. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/orm/fields.py +0 -0
  111. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/__feedback/widget.js +0 -0
  112. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/css/tina4.css +0 -0
  113. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/favicon.ico +0 -0
  114. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/images/logo.svg +0 -0
  115. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  116. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/frond.js +0 -0
  117. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/frond.min.js +0 -0
  118. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  119. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/tina4.min.js +0 -0
  120. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/js/tina4js.min.js +0 -0
  121. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/swagger/index.html +0 -0
  122. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  123. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/query_builder/__init__.py +0 -0
  124. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue/job.py +0 -0
  125. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/queue_backends/__init__.py +0 -0
  126. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/__init__.py +0 -0
  127. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Attachment.py +0 -0
  128. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Channel.py +0 -0
  129. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/ChannelMember.py +0 -0
  130. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Message.py +0 -0
  131. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/Workspace.py +0 -0
  132. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/models/__init__.py +0 -0
  133. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/realtime/storage.py +0 -0
  134. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  135. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_badges.scss +0 -0
  136. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  137. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_cards.scss +0 -0
  138. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_forms.scss +0 -0
  139. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_grid.scss +0 -0
  140. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_modals.scss +0 -0
  141. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_nav.scss +0 -0
  142. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_pagination.scss +0 -0
  143. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_reset.scss +0 -0
  144. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_tables.scss +0 -0
  145. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_typography.scss +0 -0
  146. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  147. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/_variables.scss +0 -0
  148. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/base.scss +0 -0
  149. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/colors.scss +0 -0
  150. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/scss/tina4css/tina4.scss +0 -0
  151. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/seeder/__init__.py +0 -0
  152. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/service/__init__.py +0 -0
  153. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/components/crud.twig +0 -0
  154. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  155. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  156. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/python/Dockerfile +0 -0
  157. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  158. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/302.twig +0 -0
  159. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/401.twig +0 -0
  160. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/403.twig +0 -0
  161. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/404.twig +0 -0
  162. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/500.twig +0 -0
  163. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/502.twig +0 -0
  164. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/503.twig +0 -0
  165. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/errors/base.twig +0 -0
  166. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/frontend/README.md +0 -0
  167. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/templates/readme.md +0 -0
  168. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/test/__init__.py +0 -0
  169. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/test_client/__init__.py +0 -0
  170. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  171. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  172. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  173. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  174. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  175. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  176. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  177. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  178. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  179. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  180. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  181. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  182. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/validator/__init__.py +0 -0
  183. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/websocket/backplane.py +0 -0
  184. {tina4_python-3.13.94 → tina4_python-3.13.96}/tina4_python/wsdl/__init__.py +0 -0
@@ -10,6 +10,13 @@
10
10
  .env.local
11
11
  /public/css/test.css
12
12
  /sessions/
13
+ # Runtime session files written by the suite. This was the ONE gap of the four:
14
+ # php ignores /data/, node ignores data/, ruby ignores sessions/ (and defaults to
15
+ # pwd/sessions anyway) - only python left data/sessions/ visible, so a plain test
16
+ # run dirtied the tree and `git add -A` committed session payloads. It did: five
17
+ # of them are tracked on feature/audit-auth. FileSessionHandler already does
18
+ # mkdir(parents=True, exist_ok=True), so a fresh clone needs nothing here.
19
+ /data/sessions/
13
20
  /tests/secrets/domain.cert
14
21
  /tests/secrets/private.key
15
22
  /tests/secrets/public.key
@@ -98,3 +105,8 @@ example/src/**/__pycache__/
98
105
  # relative to cwd would create these under the repo. Never commit them.
99
106
  /private/
100
107
  /var/folders/
108
+ /data/sessions/
109
+
110
+ # Local file-backend queue store - runtime artefacts, never committed.
111
+ # Four probe .queue-data files were swept in by `git add -A` on 2026-08-03.
112
+ data/queue/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.13.94
3
+ Version: 3.13.96
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
@@ -14,6 +14,8 @@ Requires-Dist: pymssql>=2.3; extra == 'all-db'
14
14
  Requires-Dist: pyodbc>=5.0; extra == 'all-db'
15
15
  Provides-Extra: firebird
16
16
  Requires-Dist: firebird-driver>=1.10; extra == 'firebird'
17
+ Provides-Extra: kafka
18
+ Requires-Dist: confluent-kafka>=2.0; extra == 'kafka'
17
19
  Provides-Extra: memcache
18
20
  Requires-Dist: pymemcache>=4.0; extra == 'memcache'
19
21
  Provides-Extra: mongo
@@ -26,8 +28,25 @@ Provides-Extra: odbc
26
28
  Requires-Dist: pyodbc>=5.0; extra == 'odbc'
27
29
  Provides-Extra: postgres
28
30
  Requires-Dist: psycopg2-binary>=2.9; extra == 'postgres'
31
+ Provides-Extra: rabbitmq
32
+ Requires-Dist: pika>=1.3; extra == 'rabbitmq'
29
33
  Provides-Extra: redis
30
34
  Requires-Dist: redis>=5.0; extra == 'redis'
35
+ Provides-Extra: s3
36
+ Requires-Dist: boto3>=1.34; extra == 's3'
37
+ Provides-Extra: test
38
+ Requires-Dist: boto3>=1.34; extra == 'test'
39
+ Requires-Dist: confluent-kafka>=2.0; extra == 'test'
40
+ Requires-Dist: firebird-driver>=1.10; extra == 'test'
41
+ Requires-Dist: mysql-connector-python>=9.0; extra == 'test'
42
+ Requires-Dist: openapi-spec-validator>=0.7; extra == 'test'
43
+ Requires-Dist: pika>=1.3; extra == 'test'
44
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'test'
45
+ Requires-Dist: pymemcache>=4.0; extra == 'test'
46
+ Requires-Dist: pymongo>=4.0; extra == 'test'
47
+ Requires-Dist: pymssql>=2.3; extra == 'test'
48
+ Requires-Dist: pyodbc>=5.0; extra == 'test'
49
+ Requires-Dist: redis>=5.0; extra == 'test'
31
50
  Description-Content-Type: text/markdown
32
51
 
33
52
  <p align="center">
@@ -116,7 +135,7 @@ Every feature is built from scratch -- no pip install, no node_modules, no third
116
135
  | **Core HTTP** (7) | Router with path params (`{id:int}`, `{p:path}`), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
117
136
  | **Database** (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird: unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
118
137
  | **ORM** (7) | Active Record with typed fields, relationships (`has_one`/`has_many`/`belongs_to`), soft delete, QueryBuilder + MongoDB support, Auto-CRUD generator, migrations with rollback |
119
- | **Auth & Security** (5) | JWT (HS256/RS256), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
138
+ | **Auth & Security** (5) | JWT (HS256/HS384/HS512, zero-dependency; RS256 opt-in, needs `cryptography`), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
120
139
  | **Templating** (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8x faster), SCSS auto-compilation, built-in CSS (~24 KB) |
121
140
  | **API & Integration** (5) | HTTP client (zero-dep), GraphQL with ORM auto-schema + GraphiQL IDE, WSDL/SOAP with auto WSDL, WebSocket (RFC 6455) + Redis backplane, MCP server (24 dev tools) |
122
141
  | **Background** (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters; service runner; event system (on/emit/once/off) |
@@ -84,7 +84,7 @@ Every feature is built from scratch -- no pip install, no node_modules, no third
84
84
  | **Core HTTP** (7) | Router with path params (`{id:int}`, `{p:path}`), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
85
85
  | **Database** (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird: unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
86
86
  | **ORM** (7) | Active Record with typed fields, relationships (`has_one`/`has_many`/`belongs_to`), soft delete, QueryBuilder + MongoDB support, Auto-CRUD generator, migrations with rollback |
87
- | **Auth & Security** (5) | JWT (HS256/RS256), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
87
+ | **Auth & Security** (5) | JWT (HS256/HS384/HS512, zero-dependency; RS256 opt-in, needs `cryptography`), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
88
88
  | **Templating** (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8x faster), SCSS auto-compilation, built-in CSS (~24 KB) |
89
89
  | **API & Integration** (5) | HTTP client (zero-dep), GraphQL with ORM auto-schema + GraphiQL IDE, WSDL/SOAP with auto WSDL, WebSocket (RFC 6455) + Redis backplane, MCP server (24 dev tools) |
90
90
  | **Background** (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters; service runner; event system (on/emit/once/off) |
@@ -0,0 +1,125 @@
1
+ [project]
2
+ name = "tina4-python"
3
+ version = "3.13.96"
4
+ description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
+ authors = [
6
+ {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.12"
10
+ license = {text = "MIT"}
11
+
12
+ # ZERO core dependencies — this is v3's defining feature
13
+ dependencies = []
14
+
15
+ [project.optional-dependencies]
16
+ # Database drivers — install what you need
17
+ postgres = ["psycopg2-binary>=2.9"]
18
+ mysql = ["mysql-connector-python>=9.0"]
19
+ mssql = ["pymssql>=2.3"]
20
+ firebird = ["firebird-driver>=1.10"]
21
+ mongo = ["pymongo>=4.0"]
22
+ odbc = ["pyodbc>=5.0"]
23
+
24
+ # Session backends — install what you need
25
+ redis = ["redis>=5.0"]
26
+ memcache = ["pymemcache>=4.0"]
27
+
28
+ # File-storage backends — install what you need.
29
+ # The `local` filesystem backend is the zero-dependency default; S3Storage
30
+ # (TINA4_STORAGE_BACKEND=s3) is opt-in and imports boto3 lazily.
31
+ s3 = ["boto3>=1.34"]
32
+
33
+ # Queue backends — install what you need
34
+ kafka = ["confluent-kafka>=2.0"]
35
+ rabbitmq = ["pika>=1.3"]
36
+
37
+ # All database drivers
38
+ all-db = [
39
+ "psycopg2-binary>=2.9",
40
+ "mysql-connector-python>=9.0",
41
+ "pymssql>=2.3",
42
+ "firebird-driver>=1.10",
43
+ "pymongo>=4.0",
44
+ "pyodbc>=5.0",
45
+ ]
46
+
47
+ # Every client the TEST SUITE needs against live services.
48
+ #
49
+ # These were installed ad hoc by CI (`uv pip install pymongo confluent-kafka
50
+ # pika ...`) and declared nowhere, so a fresh host had no way to know what the
51
+ # suite requires. confluent-kafka and pika in particular were in NO extra: the
52
+ # lab host's first full-suite run failed with three "confluent-kafka not
53
+ # installed" errors, correctly raised by the TINA4_REQUIRE_SERVICES gate, and
54
+ # `uv sync --all-extras` could not have fixed it because the dependency was not
55
+ # expressed anywhere. One declared extra means `uv sync --extra test` is
56
+ # reproducible on any machine.
57
+ #
58
+ # Includes firebird-driver even though the Firebird CI job installs it on its
59
+ # own: `uv sync --extra X` PRUNES anything outside the named extras, so a test
60
+ # extra that omitted it would silently uninstall it on a host that had it.
61
+ #
62
+ # boto3 is here for exactly that reason, MEASURED on the lab 2026-08-06
63
+ # (Ubuntu 24.04.4 LTS x86_64, Python 3.13.3): it was declared in NO extra, but
64
+ # happened to be installed ad hoc, so the two REAL-MinIO tests in
65
+ # tests/test_realtime_files.py were passing. Running `uv sync --extra test`
66
+ # pruned boto3 (plus s3transfer and jmespath) and those tests went straight from
67
+ # "2 passed" to "2 skipped" -- with the suite still GREEN under
68
+ # TINA4_REQUIRE_SERVICES=1, because the skip reason matched neither a service
69
+ # keyword nor an unavailable hint in the conftest gate. Live S3 coverage
70
+ # evaporated silently. Declaring it here is the fix; tests/conftest.py now also
71
+ # recognises a missing boto3 so the gate can never miss it again.
72
+ test = [
73
+ "psycopg2-binary>=2.9",
74
+ "mysql-connector-python>=9.0",
75
+ "pymssql>=2.3",
76
+ "firebird-driver>=1.10",
77
+ "pymongo>=4.0",
78
+ "pyodbc>=5.0",
79
+ "redis>=5.0",
80
+ "pymemcache>=4.0",
81
+ "confluent-kafka>=2.0",
82
+ "pika>=1.3",
83
+ "boto3>=1.34",
84
+ # TEST-ONLY: validates the generated OpenAPI document against the real
85
+ # OpenAPI 3.0/3.1 schema (tests/test_swagger_contract.py). NEVER a framework
86
+ # runtime dependency — the swagger generator itself stays zero-dependency.
87
+ "openapi-spec-validator>=0.7",
88
+ ]
89
+
90
+ [dependency-groups]
91
+ dev = [
92
+ "bottle>=0.13.4",
93
+ "cython>=3.2.4",
94
+ "django>=6.0.3",
95
+ "fastapi>=0.135.1",
96
+ "flask>=3.1.3",
97
+ "pytest>=8.0",
98
+ "pytest-asyncio>=0.23",
99
+ "pytest-cov>=5.0",
100
+ "reportlab>=4.4.10",
101
+ "ruff>=0.4",
102
+ "setuptools>=82.0.1",
103
+ "uvicorn>=0.42.0",
104
+ ]
105
+
106
+ [build-system]
107
+ requires = ["hatchling"]
108
+ build-backend = "hatchling.build"
109
+
110
+ [tool.hatch.build]
111
+ include = ["tina4_python/**/*"]
112
+
113
+ [project.scripts]
114
+ tina4python = "tina4_python.cli:main"
115
+
116
+ [tool.pytest.ini_options]
117
+ asyncio_mode = "auto"
118
+ testpaths = ["tests"]
119
+ markers = [
120
+ "slow: a test that needs real wall-clock time to reproduce a timing bug (deselect with -m 'not slow')",
121
+ ]
122
+
123
+ [tool.ruff]
124
+ target-version = "py312"
125
+ line-length = 120
@@ -514,8 +514,11 @@ TINA4_TOKEN_LIMIT is used to set the session time, default 60 minutes
514
514
 
515
515
  Set `TINA4_SESSION_BACKEND` to choose a backend:
516
516
 
517
- The value is the backend NAME, not a class name. An unknown value falls back to
518
- the file backend without raising.
517
+ The value is the backend NAME, not a class name, and it is normalised (trimmed and
518
+ lowercased), so " Redis " resolves. An UNRECOGNISED value raises `ValueError` at
519
+ startup, naming the bad value and the valid ones -- it used to fall back to the
520
+ file backend silently, which turned a typo into sessions quietly written to local
521
+ disk. Unset or blank still means file.
519
522
 
520
523
  | Value | Backend | Required package |
521
524
  |---------|---------|-----------------|
@@ -818,7 +821,7 @@ row = db.fetch_one("SELECT * FROM users WHERE id = ?", [1])
818
821
  # Result methods
819
822
  result.to_json() # JSON string
820
823
  result.to_array() # List of dicts
821
- result.to_paginate() # Dict with records, count, limit, offset
824
+ result.to_paginate() # Dict: records, total, page, per_page, total_pages, limit, offset (takes no args; ADR-0043)
822
825
  result.to_csv() # CSV string
823
826
 
824
827
  # Transactions
@@ -1641,17 +1644,23 @@ TINA4_TOKEN_LIMIT=60 # Token lifetime in minutes (default: 60)
1641
1644
  TINA4_DATABASE_URL=sqlite:///app.db # Connection URL (driver://host:port/database)
1642
1645
  TINA4_DATABASE_USERNAME= # DB username (for PostgreSQL, MySQL, etc.)
1643
1646
  TINA4_DATABASE_PASSWORD= # DB password
1647
+ TINA4_DATABASE_CONNECT_TIMEOUT=10 # Seconds a database connect may block (default 10). Set <= 0 to wait
1648
+ # indefinitely (the old behaviour); a non-numeric value warns and uses 10.
1649
+ # On expiry the error names the host, port, elapsed seconds and this variable.
1650
+ # Applies to postgres/mysql/mssql/odbc/firebird, and to mongodb's deferred
1651
+ # connect. SQLite is exempt: it opens a local file, there is no peer to hang on
1644
1652
 
1645
1653
  # Framework
1646
1654
  TINA4_DEBUG=true # Enable dev mode (toolbar, live reload, error overlay)
1647
1655
  TINA4_LOG_LEVEL=INFO # Log verbosity: ALL, DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO)
1648
1656
  TINA4_LOCALE=en # Language for framework messages (en, fr, af, zh, ja, es)
1649
- TINA4_DEFAULT_WEBSERVER=FALSE # Set to TRUE to use Tina4's built-in webserver instead of ASGI
1657
+ TINA4_DEFAULT_WEBSERVER=FALSE # TRUE pins Tina4's built-in asyncio webserver even when a production ASGI server (uvicorn/hypercorn/granian) is installed. Unset/FALSE: a production server is used when one is importable and TINA4_DEBUG is off
1658
+ TINA4_SHUTDOWN_TIMEOUT=30 # Seconds to drain in-flight requests on SIGTERM/SIGINT (default 30). Honoured on the built-in server, and mapped onto uvicorn's timeout_graceful_shutdown / hypercorn's graceful_timeout. granian has no drain deadline, so it is not honoured there (logged at startup). A non-positive or unparseable value warns and uses 30
1650
1659
  TINA4_OVERRIDE_CLIENT=false # Set to true to allow running without tina4 CLI (e.g. Docker)
1651
1660
  HOST_NAME=localhost:7145
1652
1661
 
1653
1662
  # Sessions
1654
- TINA4_SESSION_BACKEND=file # file, redis, valkey, mongodb, database (unknown value falls back to file)
1663
+ TINA4_SESSION_BACKEND=file # file, redis, valkey, mongodb, memcached, database (an unknown value RAISES; unset/blank = file)
1655
1664
  TINA4_SESSION_SAMESITE=Lax # SameSite attribute for session cookies (default: Lax)
1656
1665
 
1657
1666
  # Swagger/OpenAPI
@@ -1713,7 +1722,7 @@ anywhere (the production on/off switch, wired for real in 3.13.40).
1713
1722
 
1714
1723
  ## CORS
1715
1724
 
1716
- Built-in all origins allowed by default. CORS headers and OPTIONS pre-flight are handled automatically.
1725
+ Built-in, and it DENIES by default (ADR-0018). Set `TINA4_CORS_ORIGINS` to the origins you want to allow (comma-separated), or `*` for any. With it unset no `Access-Control-Allow-Origin` is sent and the browser blocks the cross-origin request. `TINA4_CORS_CREDENTIALS=true` is never sent alongside a `*` origin (the Fetch Standard forbids the pair). OPTIONS pre-flight is handled automatically.
1717
1726
 
1718
1727
  ## SCSS Workflow
1719
1728
 
@@ -51,7 +51,7 @@ def _resolve_version() -> str:
51
51
  #
52
52
  # test_version_constant.py now asserts this literal equals the pyproject
53
53
  # version, so the release bump cannot leave it behind again.
54
- return "3.13.94"
54
+ return "3.13.95"
55
55
 
56
56
 
57
57
  __version__ = _resolve_version()
@@ -86,6 +86,7 @@ from tina4_python.core.constants import ( # noqa: E402, F401
86
86
  )
87
87
  from tina4_python.core.server import ( # noqa: E402, F401
88
88
  run, background, background_task_count, stop_all_background_tasks,
89
+ asgi,
89
90
  )
90
91
  from tina4_python.core.events import on, emit, once, off # noqa: E402, F401
91
92
  from tina4_python.env import Env # noqa: E402, F401
@@ -203,13 +203,16 @@ class Api:
203
203
  """HTTP DELETE request."""
204
204
  return self._request("DELETE", self._url(path), body)
205
205
 
206
- def send(self, method: str, path: str = "", body=None,
207
- content_type: str = "application/json") -> dict:
208
- """Generic request method — pick HTTP verb at call time.
209
-
210
- Renamed from ``send_request`` in 3.13.0 for parity with the
211
- documentation and conciseness (``api.send("PATCH", ...)`` reads
212
- cleaner than ``api.send_request("PATCH", ...)``).
206
+ def send_request(self, method: str, path: str = "", body=None,
207
+ content_type: str = "application/json") -> dict:
208
+ """Generic request method — pick the HTTP verb at call time.
209
+
210
+ ``send_request`` is the name all four frameworks share (PHP
211
+ ``sendRequest``, Ruby ``send_request``, Node ``sendRequest``). The
212
+ 3.13.0 Python-only rename to ``send`` was reverted: ``send`` can never
213
+ be the shared name because Ruby's ``send`` is ``Object#send``, the
214
+ metaprogramming primitive, so a Ruby class cannot expose an HTTP
215
+ ``send`` without shadowing it. One concept, one name, everywhere.
213
216
  """
214
217
  return self._request(method.upper(), self._url(path), body, content_type)
215
218