tina4-python 3.13.31__tar.gz → 3.13.32__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 (157) hide show
  1. {tina4_python-3.13.31 → tina4_python-3.13.32}/PKG-INFO +1 -1
  2. {tina4_python-3.13.31 → tina4_python-3.13.32}/pyproject.toml +1 -1
  3. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/__init__.py +1 -1
  4. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/cache/__init__.py +34 -1
  5. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/connection.py +31 -18
  6. {tina4_python-3.13.31 → tina4_python-3.13.32}/.gitignore +0 -0
  7. {tina4_python-3.13.31 → tina4_python-3.13.32}/README.md +0 -0
  8. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/CLAUDE.md +0 -0
  9. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/HtmlElement.py +0 -0
  10. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/Testing.py +0 -0
  11. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/ai/__init__.py +0 -0
  12. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/api/__init__.py +0 -0
  13. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/auth/__init__.py +0 -0
  14. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/rate_limiter.py +0 -0
  22. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/request.py +0 -0
  23. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/response.py +0 -0
  24. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/router.py +0 -0
  25. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/core/server.py +0 -0
  26. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/crud/__init__.py +0 -0
  27. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/__init__.py +0 -0
  28. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/adapter.py +0 -0
  29. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/mongodb.py +0 -0
  31. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/dev_admin/__init__.py +0 -0
  39. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/dev_admin/metrics.py +0 -0
  40. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/dev_admin/plan.py +0 -0
  41. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/dev_admin/project_index.py +0 -0
  42. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/docs.py +0 -0
  43. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/dotenv/__init__.py +0 -0
  44. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/env.py +0 -0
  45. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/frond/FROND.md +0 -0
  46. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/frond/__init__.py +0 -0
  47. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/frond/engine.py +0 -0
  48. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/auth/meta.json +0 -0
  49. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  50. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/database/meta.json +0 -0
  51. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  52. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/error-overlay/meta.json +0 -0
  53. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  54. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/orm/meta.json +0 -0
  55. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  56. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  57. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/queue/meta.json +0 -0
  58. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  59. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/rest-api/meta.json +0 -0
  60. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  61. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/templates/meta.json +0 -0
  62. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  63. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  64. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/graphql/__init__.py +0 -0
  65. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/i18n/__init__.py +0 -0
  66. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/mcp/__init__.py +0 -0
  67. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/mcp/protocol.py +0 -0
  68. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/mcp/tools.py +0 -0
  69. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/messenger/__init__.py +0 -0
  70. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/migration/__init__.py +0 -0
  71. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/migration/runner.py +0 -0
  72. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/orm/__init__.py +0 -0
  73. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/orm/fields.py +0 -0
  74. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/orm/model.py +0 -0
  75. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/__feedback/widget.js +0 -0
  76. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/css/tina4.css +0 -0
  77. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/css/tina4.min.css +0 -0
  78. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/favicon.ico +0 -0
  79. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/images/logo.svg +0 -0
  80. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  81. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/js/frond.js +0 -0
  82. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/js/frond.min.js +0 -0
  83. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/js/tina4-dev-admin.js +0 -0
  84. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  85. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/js/tina4.min.js +0 -0
  86. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/js/tina4js.min.js +0 -0
  87. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/swagger/index.html +0 -0
  88. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  89. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/query_builder/__init__.py +0 -0
  90. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue/__init__.py +0 -0
  91. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue/job.py +0 -0
  92. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue/kafka_backend.py +0 -0
  93. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue/lite_backend.py +0 -0
  94. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue/mongo_backend.py +0 -0
  95. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue/rabbitmq_backend.py +0 -0
  96. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue_backends/__init__.py +0 -0
  97. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue_backends/kafka_backend.py +0 -0
  98. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue_backends/mongo_backend.py +0 -0
  99. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  100. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/__init__.py +0 -0
  101. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  102. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_badges.scss +0 -0
  103. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  104. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_cards.scss +0 -0
  105. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_forms.scss +0 -0
  106. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_grid.scss +0 -0
  107. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_modals.scss +0 -0
  108. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_nav.scss +0 -0
  109. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_reset.scss +0 -0
  110. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_tables.scss +0 -0
  111. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_typography.scss +0 -0
  112. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  113. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/_variables.scss +0 -0
  114. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/base.scss +0 -0
  115. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/colors.scss +0 -0
  116. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/scss/tina4css/tina4.scss +0 -0
  117. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/seeder/__init__.py +0 -0
  118. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/service/__init__.py +0 -0
  119. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/session/__init__.py +0 -0
  120. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/session_handlers/__init__.py +0 -0
  121. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  122. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/session_handlers/redis_handler.py +0 -0
  123. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/session_handlers/valkey_handler.py +0 -0
  124. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/swagger/__init__.py +0 -0
  125. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/components/crud.twig +0 -0
  126. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  127. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  128. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/docker/python/Dockerfile +0 -0
  129. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  130. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/302.twig +0 -0
  131. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/401.twig +0 -0
  132. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/403.twig +0 -0
  133. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/404.twig +0 -0
  134. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/500.twig +0 -0
  135. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/502.twig +0 -0
  136. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/503.twig +0 -0
  137. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/errors/base.twig +0 -0
  138. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/frontend/README.md +0 -0
  139. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/templates/readme.md +0 -0
  140. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/test/__init__.py +0 -0
  141. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/test_client/__init__.py +0 -0
  142. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  143. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  144. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  145. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  146. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  147. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  148. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  149. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  150. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  151. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  152. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  153. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  154. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/validator/__init__.py +0 -0
  155. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/websocket/__init__.py +0 -0
  156. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/websocket/backplane.py +0 -0
  157. {tina4_python-3.13.31 → tina4_python-3.13.32}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.13.31
3
+ Version: 3.13.32
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.31"
3
+ version = "3.13.32"
4
4
  description = "Tina4 Python v3 — Zero-dependency, lightweight web framework"
5
5
  authors = [
6
6
  {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"}
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.13.31"
11
+ __version__ = "3.13.32"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -880,7 +880,10 @@ class ResponseCache:
880
880
  self._hits += 1
881
881
  # Move to end (most recently used)
882
882
  self._store.move_to_end(cache_key)
883
- return request, response(entry.body, entry.status_code)
883
+ remaining = max(0, int(round(entry.expires_at - time.monotonic())))
884
+ hit_response = response(entry.body, entry.status_code)
885
+ self._set_cache_headers(hit_response, "HIT", remaining)
886
+ return request, hit_response
884
887
 
885
888
  self._misses += 1
886
889
 
@@ -930,6 +933,11 @@ class ResponseCache:
930
933
  self._store[cache_key] = entry
931
934
  self._store.move_to_end(cache_key)
932
935
 
936
+ # The handler ran for this request → MISS. Advertise the TTL the
937
+ # entry was just stored with so clients can see how long the next
938
+ # HIT will live.
939
+ self._set_cache_headers(response, "MISS", cache_ttl)
940
+
933
941
  return request, response
934
942
 
935
943
  # ── Public API ───────────────────────────────────────────────
@@ -965,6 +973,31 @@ class ResponseCache:
965
973
 
966
974
  # ── Internal helpers ─────────────────────────────────────────
967
975
 
976
+ @staticmethod
977
+ def _set_cache_headers(response, status: str, ttl: int) -> None:
978
+ """Set ``X-Cache`` / ``X-Cache-TTL`` on the outgoing response.
979
+
980
+ ``status`` is ``"HIT"`` (served from cache) or ``"MISS"`` (handler
981
+ ran). ``ttl`` is the entry's remaining seconds on a HIT, or the
982
+ configured TTL on a MISS. We deliberately do NOT emit
983
+ ``Cache-Control`` — browser-cache directives are the app's call.
984
+
985
+ Works with the framework ``Response`` (``add_header``) and with any
986
+ object exposing a ``headers`` dict, so middleware stubs and other
987
+ response shapes keep working.
988
+ """
989
+ if response is None:
990
+ return
991
+ headers = {"X-Cache": status, "X-Cache-TTL": str(int(ttl))}
992
+ setter = getattr(response, "add_header", None) or getattr(response, "header", None)
993
+ if callable(setter):
994
+ for name, value in headers.items():
995
+ setter(name, value)
996
+ return
997
+ existing = getattr(response, "headers", None)
998
+ if isinstance(existing, dict):
999
+ existing.update(headers)
1000
+
968
1001
  @staticmethod
969
1002
  def _get_route_ttl(request) -> int | None:
970
1003
  """Check for a per-route cache TTL set via the @cached decorator."""
@@ -519,7 +519,7 @@ class Database:
519
519
  return adapter.execute_many(sql, params_list)
520
520
 
521
521
  def fetch(self, sql: str, params: list = None,
522
- limit: int = 100, offset: int = 0) -> DatabaseResult:
522
+ limit: int = 100, offset: int = 0, no_cache: bool = False) -> DatabaseResult:
523
523
  """Fetch rows with pagination.
524
524
 
525
525
  v3.13.11 (issue #49 Gap 3): mirror :meth:`execute` and capture
@@ -527,25 +527,32 @@ class Database:
527
527
  ``db.get_error()`` returning ``None`` even though the adapter
528
528
  had stored the failure on its own ``last_error``. Callers had
529
529
  no way to read the cause via the public API.
530
+
531
+ ``no_cache=True`` bypasses the query cache for this one call — no
532
+ lookup and no store — and runs the query straight against the DB.
533
+ Works in either cache mode (request-scoped auto-cache or persistent
534
+ DB cache). The default (``False``) preserves today's behaviour.
530
535
  """
531
- if self._cache_enabled:
536
+ if self._cache_enabled and not no_cache:
532
537
  key = self._cache_key(sql + f":L{limit}:S{offset}", params)
533
538
  cached = self._cache_get(key)
534
539
  if cached is not None:
535
540
  with self._cache_lock:
536
541
  self._cache_hits += 1
537
542
  return cached
538
- adapter = self._get_adapter()
539
- try:
540
- result = adapter.fetch(sql, params, limit, offset)
541
- self.last_error = None
542
- except Exception:
543
- self.last_error = getattr(adapter, "last_error", None) or self.last_error
544
- raise
543
+ result = self._fetch_direct(sql, params, limit, offset)
545
544
  self._cache_set(key, result)
546
545
  with self._cache_lock:
547
546
  self._cache_misses += 1
548
547
  return result
548
+ return self._fetch_direct(sql, params, limit, offset)
549
+
550
+ def _fetch_direct(self, sql: str, params: list, limit: int, offset: int) -> DatabaseResult:
551
+ """Run a fetch straight against the adapter — no cache lookup or store.
552
+
553
+ Shared by the cached and ``no_cache`` paths so error capture stays
554
+ identical regardless of caching.
555
+ """
549
556
  adapter = self._get_adapter()
550
557
  try:
551
558
  result = adapter.fetch(sql, params, limit, offset)
@@ -556,7 +563,7 @@ class Database:
556
563
  raise
557
564
 
558
565
  def fetch_all(self, sql: str, params: list = None,
559
- limit: int = 0, offset: int = 0) -> list[dict]:
566
+ limit: int = 0, offset: int = 0, no_cache: bool = False) -> list[dict]:
560
567
  """Fetch ALL rows and return the records list directly.
561
568
 
562
569
  Symmetric with ``fetch_one``. For the common case where you just
@@ -572,26 +579,32 @@ class Database:
572
579
  name says ``fetch_all``, so it returns all matching rows. Pre-v3.13.12
573
580
  silently truncated to 100. Pass an explicit ``limit=N`` to cap.
574
581
 
575
- Returns ``[]`` (not ``None``) when no rows match.
582
+ ``no_cache=True`` bypasses the query cache for this one call (see
583
+ :meth:`fetch`). Returns ``[]`` (not ``None``) when no rows match.
576
584
  """
577
- return self.fetch(sql, params, limit, offset).records
585
+ return self.fetch(sql, params, limit, offset, no_cache=no_cache).records
578
586
 
579
- def fetch_one(self, sql: str, params: list = None) -> dict | None:
580
- if self._cache_enabled:
587
+ def fetch_one(self, sql: str, params: list = None, no_cache: bool = False) -> dict | None:
588
+ """Fetch a single row as a dict, or ``None`` when no row matches.
589
+
590
+ ``no_cache=True`` bypasses the query cache for this one call — no
591
+ lookup and no store — and runs the query straight against the DB
592
+ (see :meth:`fetch`). The default (``False``) preserves today's
593
+ behaviour.
594
+ """
595
+ if self._cache_enabled and not no_cache:
581
596
  key = self._cache_key(sql + ":ONE", params)
582
597
  cached = self._cache_get(key)
583
598
  if cached is not None:
584
599
  with self._cache_lock:
585
600
  self._cache_hits += 1
586
601
  return cached
587
- adapter = self._get_adapter()
588
- result = adapter.fetch_one(sql, params)
602
+ result = self._get_adapter().fetch_one(sql, params)
589
603
  self._cache_set(key, result)
590
604
  with self._cache_lock:
591
605
  self._cache_misses += 1
592
606
  return result
593
- adapter = self._get_adapter()
594
- return adapter.fetch_one(sql, params)
607
+ return self._get_adapter().fetch_one(sql, params)
595
608
 
596
609
  def insert(self, table: str, data: dict | list) -> DatabaseResult:
597
610
  if self._cache_enabled:
File without changes