tina4-python 3.13.139__tar.gz → 3.13.140__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 (192) hide show
  1. {tina4_python-3.13.139 → tina4_python-3.13.140}/PKG-INFO +1 -1
  2. {tina4_python-3.13.139 → tina4_python-3.13.140}/pyproject.toml +1 -1
  3. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/CLAUDE.md +28 -14
  4. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/__init__.py +1 -1
  5. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/cli/__init__.py +8 -3
  6. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/connection.py +53 -23
  7. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/pool.py +8 -0
  8. {tina4_python-3.13.139 → tina4_python-3.13.140}/.gitignore +0 -0
  9. {tina4_python-3.13.139 → tina4_python-3.13.140}/COMMERCIAL-LICENSE.md +0 -0
  10. {tina4_python-3.13.139 → tina4_python-3.13.140}/LICENSE +0 -0
  11. {tina4_python-3.13.139 → tina4_python-3.13.140}/NOTICE +0 -0
  12. {tina4_python-3.13.139 → tina4_python-3.13.140}/README.md +0 -0
  13. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/.tina4-metrics.json +0 -0
  14. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/HtmlElement.py +0 -0
  15. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/Testing.py +0 -0
  16. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/_import_helper.py +0 -0
  17. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/ai/__init__.py +0 -0
  18. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/ai/client.py +0 -0
  19. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/api/__init__.py +0 -0
  20. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/auth/__init__.py +0 -0
  21. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/cache/__init__.py +0 -0
  22. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/container/__init__.py +0 -0
  23. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/context/__init__.py +0 -0
  24. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/context/chunker.py +0 -0
  25. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/__init__.py +0 -0
  26. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/cache.py +0 -0
  27. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/constants.py +0 -0
  28. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/events.py +0 -0
  29. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/middleware.py +0 -0
  30. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/port_takeover.py +0 -0
  31. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/rate_limiter.py +0 -0
  32. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/request.py +0 -0
  33. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/response.py +0 -0
  34. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/router.py +0 -0
  35. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/core/server.py +0 -0
  36. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/crud/__init__.py +0 -0
  37. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/__init__.py +0 -0
  38. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/adapter.py +0 -0
  39. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/async_api.py +0 -0
  40. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/database_url.py +0 -0
  41. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/firebird.py +0 -0
  42. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/mongodb.py +0 -0
  43. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/mssql.py +0 -0
  44. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/mysql.py +0 -0
  45. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/odbc.py +0 -0
  46. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/postgres.py +0 -0
  47. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/sql_translator.py +0 -0
  48. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/database/sqlite.py +0 -0
  49. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/debug/__init__.py +0 -0
  50. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/debug/error_overlay.py +0 -0
  51. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/__init__.py +0 -0
  52. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/metrics.py +0 -0
  53. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/plan.py +0 -0
  54. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dev_admin/project_index.py +0 -0
  55. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/docs.py +0 -0
  56. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/docstore/__init__.py +0 -0
  57. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/dotenv/__init__.py +0 -0
  58. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/env.py +0 -0
  59. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/FROND.md +0 -0
  60. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/__init__.py +0 -0
  61. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/compiler.py +0 -0
  62. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/engine.py +0 -0
  63. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/frond/parser.py +0 -0
  64. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/auth/meta.json +0 -0
  65. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  66. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/database/meta.json +0 -0
  67. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  68. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/error-overlay/meta.json +0 -0
  69. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  70. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/orm/meta.json +0 -0
  71. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  72. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  73. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/queue/meta.json +0 -0
  74. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  75. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/rest-api/meta.json +0 -0
  76. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  77. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/templates/meta.json +0 -0
  78. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  79. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  80. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/__init__.py +0 -0
  81. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapter.py +0 -0
  82. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/__init__.py +0 -0
  83. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/arango.py +0 -0
  84. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/bolt.py +0 -0
  85. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/adapters/ultipa.py +0 -0
  86. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graph/graph_url.py +0 -0
  87. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/graphql/__init__.py +0 -0
  88. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/i18n/__init__.py +0 -0
  89. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mcp/__init__.py +0 -0
  90. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mcp/protocol.py +0 -0
  91. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mcp/tools.py +0 -0
  92. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/messenger/__init__.py +0 -0
  93. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/migration/__init__.py +0 -0
  94. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/migration/runner.py +0 -0
  95. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mqtt/__init__.py +0 -0
  96. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/mqtt/message.py +0 -0
  97. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/__init__.py +0 -0
  98. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/async_api.py +0 -0
  99. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/collection.py +0 -0
  100. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/fields.py +0 -0
  101. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/model.py +0 -0
  102. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/orm/point.py +0 -0
  103. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/THIRD-PARTY-NOTICES.md +0 -0
  104. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/__feedback/widget.js +0 -0
  105. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/bundled-component-provenance.json +0 -0
  106. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/css/tina4.css +0 -0
  107. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/css/tina4.min.css +0 -0
  108. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/favicon.ico +0 -0
  109. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/images/logo.svg +0 -0
  110. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  111. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/frond.js +0 -0
  112. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/frond.min.js +0 -0
  113. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  114. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4.min.js +0 -0
  115. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/js/tina4js.min.js +0 -0
  116. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/swagger-ui-3.10.0-original-notice.txt +0 -0
  117. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/swagger-ui-5.17.14-NOTICE.txt +0 -0
  118. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/swagger-ui-Apache-2.0.txt +0 -0
  119. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-css-MIT.txt +0 -0
  120. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-dev-admin-components.json +0 -0
  121. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-dev-admin-third-party.txt +0 -0
  122. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/licenses/tina4-js-MIT.txt +0 -0
  123. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/swagger/index.html +0 -0
  124. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  125. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/__init__.py +0 -0
  126. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/_base.py +0 -0
  127. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/_cryptography.py +0 -0
  128. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/push/_libcrypto.py +0 -0
  129. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/query_builder/__init__.py +0 -0
  130. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/__init__.py +0 -0
  131. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/amqp_url.py +0 -0
  132. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/job.py +0 -0
  133. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/kafka_backend.py +0 -0
  134. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/lite_backend.py +0 -0
  135. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/mongo_backend.py +0 -0
  136. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue/rabbitmq_backend.py +0 -0
  137. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/__init__.py +0 -0
  138. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/kafka_backend.py +0 -0
  139. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/mongo_backend.py +0 -0
  140. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  141. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/__init__.py +0 -0
  142. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Attachment.py +0 -0
  143. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Channel.py +0 -0
  144. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/ChannelMember.py +0 -0
  145. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Message.py +0 -0
  146. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/Workspace.py +0 -0
  147. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/models/__init__.py +0 -0
  148. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/realtime/storage.py +0 -0
  149. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/seeder/__init__.py +0 -0
  150. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/service/__init__.py +0 -0
  151. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session/__init__.py +0 -0
  152. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/__init__.py +0 -0
  153. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/memcached_handler.py +0 -0
  154. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  155. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/redis_handler.py +0 -0
  156. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/session_handlers/valkey_handler.py +0 -0
  157. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/sso.py +0 -0
  158. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/ssrf.py +0 -0
  159. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/swagger/__init__.py +0 -0
  160. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/components/crud.twig +0 -0
  161. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  162. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  163. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/python/Dockerfile +0 -0
  164. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  165. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/302.twig +0 -0
  166. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/401.twig +0 -0
  167. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/403.twig +0 -0
  168. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/404.twig +0 -0
  169. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/500.twig +0 -0
  170. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/502.twig +0 -0
  171. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/503.twig +0 -0
  172. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/errors/base.twig +0 -0
  173. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/frontend/README.md +0 -0
  174. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/templates/readme.md +0 -0
  175. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/test/__init__.py +0 -0
  176. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/test_client/__init__.py +0 -0
  177. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  178. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  179. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  180. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  181. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  182. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  183. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  184. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  185. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  186. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  187. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  188. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  189. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/validator/__init__.py +0 -0
  190. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/websocket/__init__.py +0 -0
  191. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/websocket/backplane.py +0 -0
  192. {tina4_python-3.13.139 → tina4_python-3.13.140}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tina4-python
3
- Version: 3.13.139
3
+ Version: 3.13.140
4
4
  Summary: Tina4 Python v3 — Zero-dependency, lightweight web framework
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License-Expression: MPL-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tina4-python"
3
- version = "3.13.139"
3
+ version = "3.13.140"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -1752,25 +1752,39 @@ uv run tina4python start 8080 # Start on custom port
1752
1752
 
1753
1753
  ### CRUD Generator
1754
1754
 
1755
- Generate a complete CRUD interface (list, create, update, delete) for any database table with one call:
1755
+ Generate a complete REST CRUD interface (list, get, create, update, delete) for any ORM model.
1756
+ Flag the model with `auto_crud = True` and the routes register themselves at import time:
1757
+
1758
+ ```python
1759
+ from tina4_python.orm import ORM, IntegerField, StringField
1760
+
1761
+ class User(ORM):
1762
+ auto_crud = True # auto-registers CRUD routes for this model
1763
+ id = IntegerField(primary_key=True, auto_increment=True)
1764
+ name = StringField()
1765
+ email = StringField()
1766
+ ```
1767
+
1768
+ Or register models explicitly (or discover a whole directory):
1756
1769
 
1757
1770
  ```python
1758
1771
  from tina4_python.crud import AutoCrud
1772
+ from src.orm.user import User
1759
1773
 
1760
- @get("/admin/users")
1761
- async def admin_users(request, response):
1762
- return response(CRUD.to_crud(request, {
1763
- "sql": "SELECT id, name, email FROM users",
1764
- "title": "User Management",
1765
- "primary_key": "id",
1766
- }))
1774
+ AutoCrud.register(User) # routes under /api/user
1775
+ AutoCrud.register(User, prefix="/api/v2") # or a custom prefix
1776
+ AutoCrud.discover("src/orm", prefix="/api") # every ORM model in a directory
1767
1777
  ```
1768
1778
 
1769
- This auto-generates:
1770
- - Searchable, paginated HTML table with Tina4 CSS
1771
- - Create / Edit / Delete modals with form tokens
1772
- - 4 RESTful API routes (GET list, POST create, POST update, DELETE)
1773
- - Per-table Twig template in `src/templates/crud/` (customisable after generation)
1779
+ This auto-generates per model:
1780
+ - `GET /api/{table}` — paginated list (returns the `to_paginate()` envelope)
1781
+ - `GET /api/{table}/{id}` — one record by primary key
1782
+ - `POST /api/{table}` — create
1783
+ - `PUT /api/{table}/{id}` — update
1784
+ - `DELETE /api/{table}/{id}` — delete
1785
+
1786
+ Write routes are secure by default (they require a valid token); pass `public=True`
1787
+ to `register` / `discover` to open them.
1774
1788
 
1775
1789
  ### ORM Table Creation
1776
1790
 
@@ -1799,7 +1813,7 @@ uv run tina4python migrate
1799
1813
 
1800
1814
  | Need | Use |
1801
1815
  |------|-----|
1802
- | Quick admin UI for a table | `CRUD.to_crud()` |
1816
+ | Auto REST CRUD for a model | `auto_crud = True` / `AutoCrud.register()` |
1803
1817
  | Schema-first database design | Migration files |
1804
1818
  | Code-first database design | `ORM.create_table()` |
1805
1819
  | New project from scratch | `tina4python init` |
@@ -57,7 +57,7 @@ def _resolve_version() -> str:
57
57
  #
58
58
  # test_version_constant.py now asserts this literal equals the pyproject
59
59
  # version, so the release bump cannot leave it behind again.
60
- return "3.13.139"
60
+ return "3.13.140"
61
61
 
62
62
 
63
63
  __version__ = _resolve_version()
@@ -1970,7 +1970,12 @@ def _gen_crud(name: str, flags: dict):
1970
1970
  """
1971
1971
  fields = _fields_or_default(flags.get("fields", ""))
1972
1972
  table = _resolve_table(name, flags)
1973
- route_name = table + "s" # routes are plural
1973
+ # Routes are the SINGLE plural of the singular base — pluralise(snake(name)) —
1974
+ # never `table + "s"`. When the table was pluralised to escape a SQL reserved
1975
+ # word (Order -> table `orders`), `table + "s"` double-pluralised the route,
1976
+ # file and template to `orderss`. Deriving from the singular base keeps one
1977
+ # consistent rule: Order -> orders, Product -> products.
1978
+ route_name = _pluralize_table(_to_snake(name))
1974
1979
 
1975
1980
  print(f"\n Generating CRUD for {name}...\n")
1976
1981
 
@@ -3244,7 +3249,7 @@ def _gen_form(name: str, flags: dict = None):
3244
3249
  flags = flags or {}
3245
3250
  fields = _fields_or_default(flags.get("fields", ""))
3246
3251
  table = _resolve_table(name, flags)
3247
- route_name = table + "s"
3252
+ route_name = _pluralize_table(_to_snake(name)) # single plural of the singular base (never table + "s")
3248
3253
 
3249
3254
  # Input type mapping
3250
3255
  input_types = {
@@ -3328,7 +3333,7 @@ def _gen_view(name: str, flags: dict = None):
3328
3333
  flags = flags or {}
3329
3334
  fields = _fields_or_default(flags.get("fields", ""))
3330
3335
  table = _resolve_table(name, flags)
3331
- route_name = table + "s"
3336
+ route_name = _pluralize_table(_to_snake(name)) # single plural of the singular base (never table + "s")
3332
3337
 
3333
3338
  target = Path("src/templates/pages")
3334
3339
  target.mkdir(parents=True, exist_ok=True)
@@ -66,17 +66,36 @@ current_route: contextvars.ContextVar = contextvars.ContextVar("tina4_current_ro
66
66
  _warned_routes: set = set()
67
67
 
68
68
 
69
+ def _running_event_loop():
70
+ """The event loop running on this thread, or ``None`` when there is none.
71
+
72
+ A supported replacement for the private ``asyncio._get_running_loop()``:
73
+ ``asyncio.get_running_loop()`` answers on a running loop and raises
74
+ ``RuntimeError`` off one (ADR-0074). It runs only after the TINA4_DEBUG
75
+ gate below, so its off-loop ``RuntimeError`` never fires on the hot sync
76
+ path in production.
77
+ """
78
+ try:
79
+ return asyncio.get_running_loop()
80
+ except RuntimeError:
81
+ return None
82
+
83
+
69
84
  def _warn_if_blocking_the_event_loop(operation: str) -> None:
70
85
  """Debug-mode warning: a sync DB call on a thread that is running an event
71
86
  loop freezes every other request on that loop until the statement returns.
72
87
 
73
- Called only once a running loop is detected, so the check costs one C call
74
- on every other thread (sync routes run in worker threads, and the async API
75
- runs its statements on the pool's own workers - neither has a loop).
88
+ Gated on TINA4_DEBUG first, so production pays nothing; only then does it
89
+ look for a running loop. Callers invoke it unconditionally - the loop check
90
+ lives here, not at every call site, so the supported (raising)
91
+ ``asyncio.get_running_loop()`` never touches the hot sync path outside
92
+ debug mode (ADR-0074).
76
93
  """
77
94
  from tina4_python.dotenv import is_truthy
78
95
  if not is_truthy(os.environ.get("TINA4_DEBUG", "")):
79
96
  return
97
+ if _running_event_loop() is None:
98
+ return
80
99
  route = current_route.get() or "(no route - a coroutine outside a request)"
81
100
  if route in _warned_routes:
82
101
  return
@@ -430,11 +449,13 @@ class Database(DatabaseAsyncMixin):
430
449
  sqlite:////absolute/path.db → /absolute/path.db (absolute)
431
450
  sqlite:///C:/Users/app.db → C:/Users/app.db (Windows absolute)
432
451
 
433
- Directories are auto-created ONLY when the resolved path is
434
- inside the current working directory (the project root). We
435
- never try to ``os.makedirs`` at root (``/data``, ``C:\\data``)
436
- — that's both hostile on read-only filesystems and not what
437
- any project actually wants.
452
+ Directories are auto-created (mode ``0775``, masked by the umask)
453
+ ONLY when the resolved parent stays inside the current working
454
+ directory (the project root). An absolute path is trusted and
455
+ never auto-created. A RELATIVE path whose parent resolves OUTSIDE
456
+ cwd (``../../etc/foo.db``) is REFUSED with a ``ValueError`` — Tina4
457
+ never ``os.makedirs`` outside the project, which is both hostile on
458
+ read-only filesystems and a latent footgun (ADR-0086).
438
459
  """
439
460
  parsed = urlparse(self.url)
440
461
 
@@ -500,10 +521,25 @@ class Database(DatabaseAsyncMixin):
500
521
  # Relative — resolve under the project root (cwd).
501
522
  cwd = os.getcwd()
502
523
  path = os.path.join(cwd, stripped)
503
- # Only auto-create subdirectories *inside* cwd.
524
+ # Create the parent, but ONLY when it stays inside cwd (ADR-0086).
525
+ # A relative path whose parent resolves OUTSIDE cwd (e.g.
526
+ # "../../etc/foo.db") is REFUSED loudly here — never a silent mkdir
527
+ # outside the project (the PHP/Ruby footgun) and never a path that
528
+ # only fails cryptically when the driver opens it (the old Python
529
+ # silent-skip). Absolute paths are trusted and never reach this
530
+ # branch. The created directory is mode 0775 (masked by the umask),
531
+ # matching every framework.
504
532
  directory = os.path.dirname(path)
505
- if directory and os.path.commonpath([os.path.abspath(directory), cwd]) == cwd:
506
- os.makedirs(directory, exist_ok=True)
533
+ if directory:
534
+ if os.path.commonpath([os.path.abspath(directory), cwd]) != cwd:
535
+ raise ValueError(
536
+ f"SQLite path {self.url!r} resolves outside the project "
537
+ f"directory: {os.path.abspath(directory)!r} is not within "
538
+ f"{cwd!r}. Tina4 refuses to create directories outside the "
539
+ f"project (ADR-0086). Use an absolute path for a database "
540
+ f"that lives outside the project."
541
+ )
542
+ os.makedirs(directory, mode=0o775, exist_ok=True)
507
543
 
508
544
  return path
509
545
 
@@ -663,8 +699,7 @@ class Database(DatabaseAsyncMixin):
663
699
  in this context, so nested facade calls land on the same connection;
664
700
  ``_release`` returns it.
665
701
  """
666
- if asyncio._get_running_loop() is not None:
667
- _warn_if_blocking_the_event_loop(operation)
702
+ _warn_if_blocking_the_event_loop(operation)
668
703
  borrow = self._borrowed.get()
669
704
  if borrow is not None:
670
705
  return borrow.adapter, None
@@ -675,8 +710,7 @@ class Database(DatabaseAsyncMixin):
675
710
  """``_acquire`` for a single adapter call that never calls back into this
676
711
  facade (fetch, fetch_one, execute, insert, delete): nothing nested can
677
712
  need the pin, so skip recording it. ``token`` is the adapter to return."""
678
- if asyncio._get_running_loop() is not None:
679
- _warn_if_blocking_the_event_loop(operation)
713
+ _warn_if_blocking_the_event_loop(operation)
680
714
  borrow = self._borrowed.get()
681
715
  if borrow is not None:
682
716
  return borrow.adapter, None
@@ -705,8 +739,7 @@ class Database(DatabaseAsyncMixin):
705
739
  commit()/rollback() - the write is uncommitted, so the connection that
706
740
  holds it must be the one the commit lands on."""
707
741
  if self._manual_commit and self._borrowed.get() is None:
708
- if asyncio._get_running_loop() is not None:
709
- _warn_if_blocking_the_event_loop(operation)
742
+ _warn_if_blocking_the_event_loop(operation)
710
743
  adapter = self._pool.checkout()
711
744
  self._borrowed.set(_Borrow(adapter, sticky=True, pool=self._pool))
712
745
  return adapter, None
@@ -1172,8 +1205,7 @@ class Database(DatabaseAsyncMixin):
1172
1205
  borrow.depth += 1
1173
1206
  return
1174
1207
  if borrow is None:
1175
- if asyncio._get_running_loop() is not None:
1176
- _warn_if_blocking_the_event_loop("start_transaction")
1208
+ _warn_if_blocking_the_event_loop("start_transaction")
1177
1209
  self._begin_on(self._pool.checkout())
1178
1210
  return
1179
1211
  # Inside an operation's (or a manual-commit write's) borrow: open the
@@ -1223,8 +1255,7 @@ class Database(DatabaseAsyncMixin):
1223
1255
  # Inner commit of an ignored nested begin — just unwind the depth.
1224
1256
  borrow.depth -= 1
1225
1257
  return
1226
- if asyncio._get_running_loop() is not None:
1227
- _warn_if_blocking_the_event_loop("commit")
1258
+ _warn_if_blocking_the_event_loop("commit")
1228
1259
  try:
1229
1260
  borrow.adapter.commit()
1230
1261
  self.last_error = None
@@ -1250,8 +1281,7 @@ class Database(DatabaseAsyncMixin):
1250
1281
  adapter.rollback()
1251
1282
  self.last_error = None
1252
1283
  return
1253
- if asyncio._get_running_loop() is not None:
1254
- _warn_if_blocking_the_event_loop("rollback")
1284
+ _warn_if_blocking_the_event_loop("rollback")
1255
1285
  try:
1256
1286
  borrow.adapter.rollback()
1257
1287
  self.last_error = None
@@ -43,6 +43,14 @@ def resolve_pool_size(pool: int | None) -> int:
43
43
 
44
44
  ``0`` keeps its documented meaning, "a single connection" - it is now LENT
45
45
  exclusively like any other pool, so it serialises instead of being shared.
46
+
47
+ Backward-compatibility note (ADR-0074): an UNSET ``pool=`` used to mean one
48
+ shared connection; it now means TINA4_DB_POOL, else DEFAULT_POOL_SIZE (10).
49
+ This is a deliberate behavioural fix - the old shared connection let two
50
+ concurrent requests read and commit inside each other's transaction. Sync
51
+ callers keep working unchanged; the only visible difference is that an app
52
+ that never set ``pool=`` now opens up to ten connections instead of one. Pin
53
+ the old single-connection behaviour with ``pool=0`` or ``TINA4_DB_POOL=0``.
46
54
  """
47
55
  if pool is None:
48
56
  raw = os.environ.get("TINA4_DB_POOL", "").strip()
File without changes
File without changes