tina4-python 3.13.134__tar.gz → 3.13.136__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 (173) hide show
  1. {tina4_python-3.13.134 → tina4_python-3.13.136}/PKG-INFO +1 -1
  2. {tina4_python-3.13.134 → tina4_python-3.13.136}/pyproject.toml +1 -1
  3. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/__init__.py +1 -1
  4. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/connection.py +58 -40
  5. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/dev_admin/__init__.py +36 -7
  6. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/swagger/index.html +1 -1
  7. {tina4_python-3.13.134 → tina4_python-3.13.136}/.gitignore +0 -0
  8. {tina4_python-3.13.134 → tina4_python-3.13.136}/README.md +0 -0
  9. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/.tina4-metrics.json +0 -0
  10. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/CLAUDE.md +0 -0
  11. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/HtmlElement.py +0 -0
  12. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/Testing.py +0 -0
  13. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/_import_helper.py +0 -0
  14. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/ai/__init__.py +0 -0
  15. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/ai/client.py +0 -0
  16. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/api/__init__.py +0 -0
  17. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/auth/__init__.py +0 -0
  18. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/cache/__init__.py +0 -0
  19. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/cli/__init__.py +0 -0
  20. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/container/__init__.py +0 -0
  21. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/context/__init__.py +0 -0
  22. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/context/chunker.py +0 -0
  23. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/__init__.py +0 -0
  24. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/cache.py +0 -0
  25. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/constants.py +0 -0
  26. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/events.py +0 -0
  27. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/middleware.py +0 -0
  28. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/port_takeover.py +0 -0
  29. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/rate_limiter.py +0 -0
  30. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/request.py +0 -0
  31. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/response.py +0 -0
  32. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/router.py +0 -0
  33. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/core/server.py +0 -0
  34. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/crud/__init__.py +0 -0
  35. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/__init__.py +0 -0
  36. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/adapter.py +0 -0
  37. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/database_url.py +0 -0
  38. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/firebird.py +0 -0
  39. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/mongodb.py +0 -0
  40. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/mssql.py +0 -0
  41. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/mysql.py +0 -0
  42. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/odbc.py +0 -0
  43. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/postgres.py +0 -0
  44. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/sql_translator.py +0 -0
  45. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/database/sqlite.py +0 -0
  46. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/debug/__init__.py +0 -0
  47. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/debug/error_overlay.py +0 -0
  48. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/dev_admin/metrics.py +0 -0
  49. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/dev_admin/plan.py +0 -0
  50. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/dev_admin/project_index.py +0 -0
  51. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/docs.py +0 -0
  52. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/docstore/__init__.py +0 -0
  53. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/dotenv/__init__.py +0 -0
  54. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/env.py +0 -0
  55. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/frond/FROND.md +0 -0
  56. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/frond/__init__.py +0 -0
  57. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/frond/compiler.py +0 -0
  58. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/frond/engine.py +0 -0
  59. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/frond/parser.py +0 -0
  60. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/auth/meta.json +0 -0
  61. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  62. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/database/meta.json +0 -0
  63. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  64. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/error-overlay/meta.json +0 -0
  65. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  66. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/orm/meta.json +0 -0
  67. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  68. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  69. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/queue/meta.json +0 -0
  70. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  71. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/rest-api/meta.json +0 -0
  72. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  73. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/templates/meta.json +0 -0
  74. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  75. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  76. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/__init__.py +0 -0
  77. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/adapter.py +0 -0
  78. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/adapters/__init__.py +0 -0
  79. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/adapters/arango.py +0 -0
  80. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/adapters/bolt.py +0 -0
  81. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/adapters/ultipa.py +0 -0
  82. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graph/graph_url.py +0 -0
  83. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/graphql/__init__.py +0 -0
  84. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/i18n/__init__.py +0 -0
  85. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/mcp/__init__.py +0 -0
  86. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/mcp/protocol.py +0 -0
  87. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/mcp/tools.py +0 -0
  88. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/messenger/__init__.py +0 -0
  89. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/migration/__init__.py +0 -0
  90. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/migration/runner.py +0 -0
  91. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/mqtt/__init__.py +0 -0
  92. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/mqtt/message.py +0 -0
  93. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/orm/__init__.py +0 -0
  94. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/orm/collection.py +0 -0
  95. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/orm/fields.py +0 -0
  96. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/orm/model.py +0 -0
  97. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/orm/point.py +0 -0
  98. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/__feedback/widget.js +0 -0
  99. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/css/tina4.css +0 -0
  100. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/css/tina4.min.css +0 -0
  101. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/favicon.ico +0 -0
  102. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/images/logo.svg +0 -0
  103. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  104. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/js/frond.js +0 -0
  105. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/js/frond.min.js +0 -0
  106. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  107. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/js/tina4.min.js +0 -0
  108. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/js/tina4js.min.js +0 -0
  109. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  110. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/push/__init__.py +0 -0
  111. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/query_builder/__init__.py +0 -0
  112. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/__init__.py +0 -0
  113. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/amqp_url.py +0 -0
  114. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/job.py +0 -0
  115. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/kafka_backend.py +0 -0
  116. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/lite_backend.py +0 -0
  117. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/mongo_backend.py +0 -0
  118. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue/rabbitmq_backend.py +0 -0
  119. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue_backends/__init__.py +0 -0
  120. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue_backends/kafka_backend.py +0 -0
  121. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue_backends/mongo_backend.py +0 -0
  122. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  123. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/__init__.py +0 -0
  124. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/models/Attachment.py +0 -0
  125. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/models/Channel.py +0 -0
  126. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/models/ChannelMember.py +0 -0
  127. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/models/Message.py +0 -0
  128. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/models/Workspace.py +0 -0
  129. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/models/__init__.py +0 -0
  130. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/realtime/storage.py +0 -0
  131. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/seeder/__init__.py +0 -0
  132. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/service/__init__.py +0 -0
  133. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/session/__init__.py +0 -0
  134. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/session_handlers/__init__.py +0 -0
  135. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/session_handlers/memcached_handler.py +0 -0
  136. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  137. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/session_handlers/redis_handler.py +0 -0
  138. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/session_handlers/valkey_handler.py +0 -0
  139. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/sso.py +0 -0
  140. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/swagger/__init__.py +0 -0
  141. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/components/crud.twig +0 -0
  142. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  143. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  144. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/docker/python/Dockerfile +0 -0
  145. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  146. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/302.twig +0 -0
  147. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/401.twig +0 -0
  148. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/403.twig +0 -0
  149. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/404.twig +0 -0
  150. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/500.twig +0 -0
  151. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/502.twig +0 -0
  152. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/503.twig +0 -0
  153. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/errors/base.twig +0 -0
  154. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/frontend/README.md +0 -0
  155. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/templates/readme.md +0 -0
  156. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/test/__init__.py +0 -0
  157. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/test_client/__init__.py +0 -0
  158. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  159. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  160. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  161. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  162. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  163. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  164. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  165. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  166. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  167. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  168. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  169. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  170. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/validator/__init__.py +0 -0
  171. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/websocket/__init__.py +0 -0
  172. {tina4_python-3.13.134 → tina4_python-3.13.136}/tina4_python/websocket/backplane.py +0 -0
  173. {tina4_python-3.13.134 → tina4_python-3.13.136}/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.134
3
+ Version: 3.13.136
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.134"
3
+ version = "3.13.136"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -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.134"
54
+ return "3.13.136"
55
55
 
56
56
 
57
57
  __version__ = _resolve_version()
@@ -10,6 +10,7 @@ Connection pooling:
10
10
  db = Database("sqlite:///data/app.db", pool=4) # 4 connections, round-robin
11
11
  """
12
12
  import hashlib
13
+ import importlib
13
14
  import os
14
15
  import threading
15
16
  import time
@@ -129,43 +130,56 @@ def register_driver(scheme: str, adapter_class: type):
129
130
  _DRIVERS[scheme] = adapter_class
130
131
 
131
132
 
132
- # Register built-in SQLite
133
+ # Register built-in SQLite eagerly. It is stdlib, so importing it drags in no
134
+ # third-party package -- the zero-dependency default carries no import cost.
133
135
  from tina4_python.database.sqlite import SQLiteAdapter
134
136
  register_driver("sqlite", SQLiteAdapter)
135
137
 
136
- # Register ODBC (lazy only fails if you actually use it without pyodbc)
137
- try:
138
- from tina4_python.database.odbc import ODBCAdapter
139
- register_driver("odbc", ODBCAdapter)
140
- except ImportError:
141
- pass # pyodbc not installed that's fine
142
-
143
- # Register PostgreSQL (psycopg2 optional)
144
- from tina4_python.database.postgres import PostgreSQLAdapter
145
- register_driver("postgresql", PostgreSQLAdapter)
146
- register_driver("postgres", PostgreSQLAdapter)
147
- register_driver("pgsql", PostgreSQLAdapter) # PDO / Laravel / Doctrine scheme name (issue #58)
148
-
149
- # Register MySQL (mysql-connector-python — optional)
150
- from tina4_python.database.mysql import MySQLAdapter
151
- register_driver("mysql", MySQLAdapter)
152
-
153
- # Register MSSQL (pymssql — optional)
154
- from tina4_python.database.mssql import MSSQLAdapter
155
- register_driver("mssql", MSSQLAdapter)
156
- register_driver("sqlserver", MSSQLAdapter)
157
-
158
- # Register Firebird (fdb — optional)
159
- from tina4_python.database.firebird import FirebirdAdapter
160
- register_driver("firebird", FirebirdAdapter)
161
-
162
- # Register MongoDB (pymongo optional)
163
- try:
164
- from tina4_python.database.mongodb import MongoDBAdapter
165
- register_driver("mongodb", MongoDBAdapter)
166
- register_driver("pymongo", MongoDBAdapter)
167
- except ImportError:
168
- pass
138
+ # Every OTHER adapter is registered LAZILY: its module -- and the third-party
139
+ # driver that module imports -- is loaded only when a URL for that scheme is
140
+ # actually connected. Importing tina4_python.database (which happens on the first
141
+ # Database(url), regardless of engine) therefore never drags psycopg2,
142
+ # firebird-driver (+ protobuf + dateutil), pymssql or pymongo into an app that
143
+ # does not use them. Connecting to Postgres loads only psycopg2; a SQLite app
144
+ # loads no third-party driver at all. This mirrors PHP/Ruby autoload and Node's
145
+ # optionalDependencies + dynamic import. Aliases share the same target so the
146
+ # canonical engine and every alias resolve to one adapter class.
147
+ _LAZY_DRIVERS: dict[str, tuple[str, str]] = {
148
+ "postgres": ("tina4_python.database.postgres", "PostgreSQLAdapter"),
149
+ "postgresql": ("tina4_python.database.postgres", "PostgreSQLAdapter"),
150
+ "pgsql": ("tina4_python.database.postgres", "PostgreSQLAdapter"), # PDO / Laravel / Doctrine (issue #58)
151
+ "mysql": ("tina4_python.database.mysql", "MySQLAdapter"),
152
+ "mssql": ("tina4_python.database.mssql", "MSSQLAdapter"),
153
+ "sqlserver": ("tina4_python.database.mssql", "MSSQLAdapter"),
154
+ "firebird": ("tina4_python.database.firebird", "FirebirdAdapter"),
155
+ "odbc": ("tina4_python.database.odbc", "ODBCAdapter"),
156
+ "mongodb": ("tina4_python.database.mongodb", "MongoDBAdapter"),
157
+ "pymongo": ("tina4_python.database.mongodb", "MongoDBAdapter"),
158
+ }
159
+
160
+
161
+ def known_drivers() -> set[str]:
162
+ """Every URL scheme the framework can resolve: eagerly-registered (sqlite,
163
+ plus anything an app registered by hand via register_driver) and the
164
+ lazily-registered built-ins that import their driver on first use."""
165
+ return set(_DRIVERS) | set(_LAZY_DRIVERS)
166
+
167
+
168
+ def _resolve_lazy_driver(scheme: str) -> None:
169
+ """Import a lazily-declared adapter and register it, on first use.
170
+
171
+ Raises a clear ValueError if the adapter's driver package is not installed,
172
+ rather than an opaque ImportError from deep in the driver.
173
+ """
174
+ module_path, class_name = _LAZY_DRIVERS[scheme]
175
+ try:
176
+ module = importlib.import_module(module_path)
177
+ except ImportError as exc:
178
+ raise ValueError(
179
+ f"Database driver '{scheme}' is unavailable: {exc}. "
180
+ f"Install the driver package and it will register automatically."
181
+ ) from exc
182
+ register_driver(scheme, getattr(module, class_name))
169
183
 
170
184
 
171
185
  class Database:
@@ -339,12 +353,16 @@ class Database:
339
353
  scheme = (urlparse(self.url).scheme or "").lower()
340
354
 
341
355
  if scheme not in _DRIVERS:
342
- available = ", ".join(_DRIVERS.keys())
343
- raise ValueError(
344
- f"Unknown database driver '{scheme}'. "
345
- f"Available: {available}. "
346
- f"Install the driver package and it will register automatically."
347
- )
356
+ if scheme in _LAZY_DRIVERS:
357
+ # First connection to this engine: import + register its adapter.
358
+ _resolve_lazy_driver(scheme)
359
+ else:
360
+ available = ", ".join(sorted(known_drivers()))
361
+ raise ValueError(
362
+ f"Unknown database driver '{scheme}'. "
363
+ f"Available: {available}. "
364
+ f"Install the driver package and it will register automatically."
365
+ )
348
366
 
349
367
  return _DRIVERS[scheme]()
350
368
 
@@ -1933,20 +1933,41 @@ def _route_count() -> int:
1933
1933
 
1934
1934
 
1935
1935
  async def _api_version_check(request, response):
1936
- """Proxy version check to PyPI to avoid browser CORS errors."""
1936
+ """Proxy the version check to PyPI so the browser dodges a CORS error.
1937
+
1938
+ A check that did not happen says so. This used to fall back to
1939
+ latest = current on any failure, and the toolbar renders that as a green
1940
+ "You are up to date!" -- so a developer several releases behind, on a
1941
+ machine with no route out, was told the opposite of the truth, and the
1942
+ toolbar's own "Could not check for updates" branch could never fire because
1943
+ the failure arrived as a success. latest is None when the check could not be
1944
+ made, and error says why.
1945
+
1946
+ The registry URL is TINA4_VERSION_CHECK_URL when set (a mirror, or a test's
1947
+ own server), else PyPI.
1948
+ """
1937
1949
  import urllib.request
1938
1950
  current = __version__
1939
- latest = current
1951
+ url = os.environ.get(
1952
+ "TINA4_VERSION_CHECK_URL", "https://pypi.org/pypi/tina4-python/json"
1953
+ )
1954
+
1955
+ def failed(why):
1956
+ return response({"current": current, "latest": None, "error": why})
1957
+
1940
1958
  try:
1941
1959
  req = urllib.request.Request(
1942
- "https://pypi.org/pypi/tina4-python/json",
1943
- headers={"User-Agent": "tina4-python/" + current},
1960
+ url, headers={"User-Agent": "tina4-python/" + current}
1944
1961
  )
1945
1962
  with urllib.request.urlopen(req, timeout=5) as resp:
1946
1963
  data = json.loads(resp.read().decode())
1947
- latest = data.get("info", {}).get("version", current)
1948
- except Exception:
1949
- pass # Offline or timeout return current as latest
1964
+ except Exception as exc: # offline, timeout, DNS, unreadable body
1965
+ return failed(str(exc) or exc.__class__.__name__)
1966
+ # Reaching PyPI is not the same as learning the version: an answer we
1967
+ # cannot read a version out of is the same lie by another route.
1968
+ latest = (data.get("info") or {}).get("version")
1969
+ if not latest:
1970
+ return failed("PyPI did not report a version")
1950
1971
  return response({"current": current, "latest": latest})
1951
1972
 
1952
1973
 
@@ -2139,6 +2160,13 @@ def toolbar_js() -> str:
2139
2160
  el.className = 't4-ok';
2140
2161
  el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> &mdash; You are up to date!';
2141
2162
  }
2163
+ // A check that did not happen is not a clean bill of health. The server
2164
+ // sends latest: null when it could not reach the registry, and saying so is
2165
+ // the whole point -- "up to date" here would be a guess dressed as a fact.
2166
+ function couldNotCheck(el, why) {
2167
+ el.className = 't4-err';
2168
+ el.textContent = 'Could not check for updates' + (why ? ' (' + why + ')' : '');
2169
+ }
2142
2170
  function checkVersion() {
2143
2171
  if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
2144
2172
  modal.style.display = 'block';
@@ -2147,6 +2175,7 @@ def toolbar_js() -> str:
2147
2175
  el.textContent = 'Checking for updates...';
2148
2176
  fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
2149
2177
  var latest = d.latest, current = d.current;
2178
+ if (!latest) { couldNotCheck(el, d.error); return; }
2150
2179
  if (latest === current) { upToDate(el, latest); return; }
2151
2180
  var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
2152
2181
  var isNewer = false, i, c, l;
@@ -71,7 +71,7 @@
71
71
 
72
72
  // Build a system
73
73
  const ui = SwaggerUIBundle({
74
- url: "{SWAGGER_ROUTE}/swagger.json",
74
+ url: "/swagger/openapi.json",
75
75
  dom_id: '#swagger-ui',
76
76
  deepLinking: true,
77
77
  presets: [