tempest-fastapi-sdk 0.63.0__tar.gz → 0.64.0__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 (429) hide show
  1. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/CHANGELOG.md +27 -0
  2. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/PKG-INFO +1 -1
  3. tempest_fastapi_sdk-0.64.0/docs/recipes/stored-files.en.md +129 -0
  4. tempest_fastapi_sdk-0.64.0/docs/recipes/stored-files.md +128 -0
  5. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/reference.md +1 -0
  6. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/mkdocs.yml +1 -0
  7. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/pyproject.toml +1 -1
  8. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/__init__.py +10 -2
  9. tempest_fastapi_sdk-0.64.0/tempest_fastapi_sdk/services/__init__.py +15 -0
  10. tempest_fastapi_sdk-0.64.0/tempest_fastapi_sdk/services/file_mixin.py +221 -0
  11. tempest_fastapi_sdk-0.64.0/tests/services/test_file_mixin.py +215 -0
  12. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/uv.lock +1 -1
  13. tempest_fastapi_sdk-0.63.0/tempest_fastapi_sdk/services/__init__.py +0 -7
  14. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.github/workflows/ci.yml +0 -0
  15. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.github/workflows/docs.yml +0 -0
  16. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.github/workflows/release-pypi.yml +0 -0
  17. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.gitignore +0 -0
  18. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.python-version +0 -0
  19. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/CLAUDE.md +0 -0
  20. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/Makefile +0 -0
  21. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/README.md +0 -0
  22. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/architecture.en.md +0 -0
  23. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/architecture.md +0 -0
  24. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/changelog.en.md +0 -0
  25. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/changelog.md +0 -0
  26. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/contributing.en.md +0 -0
  27. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/contributing.md +0 -0
  28. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/index.en.md +0 -0
  29. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/index.md +0 -0
  30. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/installation.en.md +0 -0
  31. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/installation.md +0 -0
  32. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/index.en.md +0 -0
  33. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/index.md +0 -0
  34. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/api.en.md +0 -0
  35. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/api.md +0 -0
  36. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/business-rules.en.md +0 -0
  37. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/business-rules.md +0 -0
  38. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/domain.en.md +0 -0
  39. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/domain.md +0 -0
  40. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/flows.en.md +0 -0
  41. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/flows.md +0 -0
  42. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/index.en.md +0 -0
  43. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/index.md +0 -0
  44. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/migration.en.md +0 -0
  45. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/migration.md +0 -0
  46. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/admin.en.md +0 -0
  47. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/admin.md +0 -0
  48. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/audit-trail.en.md +0 -0
  49. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/audit-trail.md +0 -0
  50. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/auth-flow.en.md +0 -0
  51. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/auth-flow.md +0 -0
  52. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/br-helpers.en.md +0 -0
  53. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/br-helpers.md +0 -0
  54. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cache.en.md +0 -0
  55. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cache.md +0 -0
  56. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cli.en.md +0 -0
  57. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cli.md +0 -0
  58. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/database.en.md +0 -0
  59. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/database.md +0 -0
  60. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/deploy-safety.en.md +0 -0
  61. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/deploy-safety.md +0 -0
  62. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/downloads.en.md +0 -0
  63. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/downloads.md +0 -0
  64. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/email.en.md +0 -0
  65. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/email.md +0 -0
  66. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/feature-flags.en.md +0 -0
  67. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/feature-flags.md +0 -0
  68. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http-client.en.md +0 -0
  69. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http-client.md +0 -0
  70. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http.en.md +0 -0
  71. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http.md +0 -0
  72. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/idempotency.en.md +0 -0
  73. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/idempotency.md +0 -0
  74. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/index.en.md +0 -0
  75. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/index.md +0 -0
  76. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/logging.en.md +0 -0
  77. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/logging.md +0 -0
  78. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/metrics.en.md +0 -0
  79. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/metrics.md +0 -0
  80. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/mfa.en.md +0 -0
  81. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/mfa.md +0 -0
  82. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/multi-tenant.en.md +0 -0
  83. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/multi-tenant.md +0 -0
  84. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/observability.en.md +0 -0
  85. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/observability.md +0 -0
  86. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/offline-sync.en.md +0 -0
  87. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/offline-sync.md +0 -0
  88. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/outbox.en.md +0 -0
  89. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/outbox.md +0 -0
  90. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/queue-tasks.en.md +0 -0
  91. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/queue-tasks.md +0 -0
  92. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/realtime.en.md +0 -0
  93. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/realtime.md +0 -0
  94. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/security.en.md +0 -0
  95. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/security.md +0 -0
  96. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/sessions.en.md +0 -0
  97. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/sessions.md +0 -0
  98. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/storage.en.md +0 -0
  99. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/storage.md +0 -0
  100. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/testing.en.md +0 -0
  101. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/testing.md +0 -0
  102. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/uploads.en.md +0 -0
  103. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/uploads.md +0 -0
  104. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/utilities.en.md +0 -0
  105. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/utilities.md +0 -0
  106. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/webpush.en.md +0 -0
  107. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/webpush.md +0 -0
  108. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/websocket.en.md +0 -0
  109. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/websocket.md +0 -0
  110. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/reference.en.md +0 -0
  111. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/roadmap.en.md +0 -0
  112. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/roadmap.md +0 -0
  113. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/tutorial.en.md +0 -0
  114. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/tutorial.md +0 -0
  115. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/scripts/extract_recipe.py +0 -0
  116. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
  117. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
  118. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/config.py +0 -0
  119. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
  120. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/router.py +0 -0
  121. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/session.py +0 -0
  122. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/site.py +0 -0
  123. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
  124. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
  125. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
  126. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
  127. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
  128. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
  129. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
  130. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
  131. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
  132. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/__init__.py +0 -0
  133. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
  134. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
  135. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
  136. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
  137. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +0 -0
  138. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
  139. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
  140. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
  141. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
  142. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
  143. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
  144. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
  145. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
  146. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
  147. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
  148. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
  149. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
  150. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
  151. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/server.py +0 -0
  152. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/static.py +0 -0
  153. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
  154. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
  155. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
  156. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/guards.py +0 -0
  157. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/locale.py +0 -0
  158. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
  159. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/router.py +0 -0
  160. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
  161. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/service.py +0 -0
  162. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/activation.html +0 -0
  163. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_error.html +0 -0
  164. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_success.html +0 -0
  165. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset.html +0 -0
  166. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_error.html +0 -0
  167. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_form.html +0 -0
  168. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_success.html +0 -0
  169. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation.html +0 -0
  170. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_error.html +0 -0
  171. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_success.html +0 -0
  172. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset.html +0 -0
  173. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_error.html +0 -0
  174. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_form.html +0 -0
  175. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_success.html +0 -0
  176. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
  177. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
  178. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/invalidation.py +0 -0
  179. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
  180. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
  181. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
  182. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
  183. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
  184. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
  185. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
  186. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
  187. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
  188. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
  189. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
  190. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
  191. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
  192. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
  193. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
  194. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
  195. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
  196. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
  197. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
  198. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
  199. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
  200. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
  201. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
  202. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
  203. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
  204. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
  205. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
  206. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
  207. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/db.py +0 -0
  208. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
  209. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
  210. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
  211. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/main.py +0 -0
  212. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/new.py +0 -0
  213. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
  214. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
  215. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/user.py +0 -0
  216. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
  217. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
  218. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
  219. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/context.py +0 -0
  220. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/enums.py +0 -0
  221. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/logging.py +0 -0
  222. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
  223. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
  224. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
  225. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
  226. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/audit.py +0 -0
  227. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/connection.py +0 -0
  228. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
  229. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
  230. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/model.py +0 -0
  231. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
  232. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/repository.py +0 -0
  233. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
  234. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
  235. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
  236. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
  237. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
  238. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
  239. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
  240. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
  241. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
  242. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/i18n.py +0 -0
  243. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
  244. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
  245. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
  246. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
  247. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
  248. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
  249. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/__init__.py +0 -0
  250. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/backends.py +0 -0
  251. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/dependencies.py +0 -0
  252. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/service.py +0 -0
  253. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/py.typed +0 -0
  254. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
  255. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
  256. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
  257. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
  258. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
  259. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
  260. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
  261. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
  262. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/services/base.py +0 -0
  263. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
  264. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
  265. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
  266. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
  267. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
  268. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
  269. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
  270. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
  271. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/settings/base.py +0 -0
  272. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
  273. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
  274. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
  275. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
  276. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
  277. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
  278. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
  279. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
  280. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
  281. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/testing/database.py +0 -0
  282. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
  283. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
  284. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/data/br_locations.json +0 -0
  285. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
  286. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
  287. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/download.py +0 -0
  288. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/email.py +0 -0
  289. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
  290. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
  291. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/locations.py +0 -0
  292. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/log.py +0 -0
  293. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
  294. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
  295. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/password.py +0 -0
  296. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
  297. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
  298. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
  299. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
  300. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
  301. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
  302. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
  303. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
  304. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
  305. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
  306. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
  307. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
  308. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/__init__.py +0 -0
  309. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/__init__.py +0 -0
  310. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_auth.py +0 -0
  311. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_forms.py +0 -0
  312. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_logs_nav.py +0 -0
  313. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_mfa.py +0 -0
  314. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_router.py +0 -0
  315. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_site.py +0 -0
  316. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_user_model.py +0 -0
  317. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/__init__.py +0 -0
  318. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_body_size.py +0 -0
  319. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_cookies.py +0 -0
  320. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_cors.py +0 -0
  321. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_csrf.py +0 -0
  322. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_dependencies_auth.py +0 -0
  323. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_graceful.py +0 -0
  324. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_handlers.py +0 -0
  325. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_health_router.py +0 -0
  326. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_idempotency.py +0 -0
  327. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_jwt_dependency.py +0 -0
  328. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_logs_router.py +0 -0
  329. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_oauth.py +0 -0
  330. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_prometheus.py +0 -0
  331. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_rate_limit.py +0 -0
  332. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_rate_limit_extras.py +0 -0
  333. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_request_id_middleware.py +0 -0
  334. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_role_dependency.py +0 -0
  335. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_server.py +0 -0
  336. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_static.py +0 -0
  337. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_tool_spec.py +0 -0
  338. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_tracing.py +0 -0
  339. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_webhooks.py +0 -0
  340. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_webhooks_rsa.py +0 -0
  341. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/__init__.py +0 -0
  342. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_guards.py +0 -0
  343. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_locale.py +0 -0
  344. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_mfa.py +0 -0
  345. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_service.py +0 -0
  346. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/__init__.py +0 -0
  347. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/test_decorator.py +0 -0
  348. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/test_invalidation.py +0 -0
  349. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/test_redis_manager.py +0 -0
  350. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/__init__.py +0 -0
  351. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_db.py +0 -0
  352. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_db_seed.py +0 -0
  353. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_docker_compose.py +0 -0
  354. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_generate.py +0 -0
  355. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_main.py +0 -0
  356. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_secrets.py +0 -0
  357. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_user.py +0 -0
  358. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/conftest.py +0 -0
  359. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/controllers/__init__.py +0 -0
  360. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/controllers/test_base.py +0 -0
  361. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/__init__.py +0 -0
  362. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/test_context.py +0 -0
  363. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/test_enums.py +0 -0
  364. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/test_logging.py +0 -0
  365. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/__init__.py +0 -0
  366. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_alembic_hooks.py +0 -0
  367. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_audit.py +0 -0
  368. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_bulk_ops.py +0 -0
  369. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_connection.py +0 -0
  370. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_migrations.py +0 -0
  371. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_mixins.py +0 -0
  372. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_model.py +0 -0
  373. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_outbox.py +0 -0
  374. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_repository.py +0 -0
  375. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_safe_upgrade.py +0 -0
  376. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_slow_query.py +0 -0
  377. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_tenant.py +0 -0
  378. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/exceptions/__init__.py +0 -0
  379. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/exceptions/test_exceptions.py +0 -0
  380. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/exceptions/test_i18n.py +0 -0
  381. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/flags/__init__.py +0 -0
  382. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/flags/test_flags.py +0 -0
  383. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/queue/__init__.py +0 -0
  384. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/queue/test_manager.py +0 -0
  385. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/__init__.py +0 -0
  386. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_base.py +0 -0
  387. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_cursor_pagination.py +0 -0
  388. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_link_headers.py +0 -0
  389. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_pagination.py +0 -0
  390. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_response.py +0 -0
  391. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/services/__init__.py +0 -0
  392. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/services/test_base.py +0 -0
  393. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sessions/__init__.py +0 -0
  394. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sessions/test_sessions.py +0 -0
  395. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/settings/__init__.py +0 -0
  396. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/settings/test_base.py +0 -0
  397. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/settings/test_mixins.py +0 -0
  398. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sse/__init__.py +0 -0
  399. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sse/test_event_stream.py +0 -0
  400. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/storage/__init__.py +0 -0
  401. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/storage/test_minio_client.py +0 -0
  402. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/tasks/__init__.py +0 -0
  403. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/tasks/test_manager.py +0 -0
  404. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/tasks/test_scheduler.py +0 -0
  405. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/testing/__init__.py +0 -0
  406. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/testing/test_database.py +0 -0
  407. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/__init__.py +0 -0
  408. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_client_ip.py +0 -0
  409. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_datetime.py +0 -0
  410. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_dict.py +0 -0
  411. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_download.py +0 -0
  412. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_email.py +0 -0
  413. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_http_client.py +0 -0
  414. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_jwt.py +0 -0
  415. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_lazy_extras.py +0 -0
  416. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_locations.py +0 -0
  417. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_log.py +0 -0
  418. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_metrics.py +0 -0
  419. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_opaque_token.py +0 -0
  420. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_password.py +0 -0
  421. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_regex.py +0 -0
  422. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_storage_backends.py +0 -0
  423. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_throttle.py +0 -0
  424. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_upload.py +0 -0
  425. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/webpush/__init__.py +0 -0
  426. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/webpush/test_dispatcher.py +0 -0
  427. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/webpush/test_schemas.py +0 -0
  428. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/websockets/__init__.py +0 -0
  429. {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/websockets/test_hub_and_router.py +0 -0
@@ -5,6 +5,33 @@ All notable changes to **tempest-fastapi-sdk** are listed below.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.64.0] — 2026-06-21
9
+
10
+ ### Added
11
+
12
+ - **`StoredFileServiceMixin[Model]`** — a service mixin that encodes the
13
+ single-key stored-file flow once, parameterized by field name:
14
+ - `set_file(ref, file, *, field, subdir=..., filename=..., keep_original_name=...)`
15
+ resolves the entity (detach-safe), uploads the new file and deletes the
16
+ old one via `UploadUtils.replace` (new written before old deleted),
17
+ writes the key back and commits.
18
+ - `clear_file(ref, *, field)` deletes the object and nulls the field
19
+ (no-op, no commit, when the field is already empty).
20
+ - `file_url(key, *, expires=...)` returns a presigned download URL, or
21
+ `None` for an empty key.
22
+
23
+ Removes the ~13-line boilerplate every service reimplements for avatars,
24
+ banners, covers and attachments. Reads its `upload_utils` and `storage`
25
+ collaborators off `self`, so the owning service keeps configuration (size
26
+ limits, allowed types, bucket). Covers the common "one key field →
27
+ presigned URL" case; resize/thumbnail pipelines, multi-variant assets and
28
+ galleries are out of scope (compose `UploadUtils` directly). See the
29
+ **Arquivo no serviço (mixin)** recipe.
30
+ - **`SupportsUpload`** and **`SupportsPresign`** — structural-typing
31
+ protocols describing the collaborators `StoredFileServiceMixin` needs
32
+ (satisfied by `UploadUtils` and `AsyncMinIOClient`), so importing the
33
+ mixin never pulls the optional `[upload]` / `[minio]` extras.
34
+
8
35
  ## [0.63.0] — 2026-06-21
9
36
 
10
37
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tempest-fastapi-sdk
3
- Version: 0.63.0
3
+ Version: 0.64.0
4
4
  Summary: Shared FastAPI building blocks: base schemas, ORM model, async repository, exceptions, pagination and settings — the conventions used across Tempest projects.
5
5
  Project-URL: Homepage, https://github.com/mauriciobenjamin700/tempest-fastapi-sdk
6
6
  Project-URL: Repository, https://github.com/mauriciobenjamin700/tempest-fastapi-sdk
@@ -0,0 +1,129 @@
1
+ # File on the service — `StoredFileServiceMixin`
2
+
3
+ An entity almost always carries **one** storage key: the user's avatar, an
4
+ event banner, a product cover, an attachment. And every service that owns one
5
+ rewrites the same dance by hand:
6
+
7
+ 1. resolve the entity,
8
+ 2. upload the new file and delete the old one,
9
+ 3. write the new key onto the model,
10
+ 4. `commit`,
11
+ 5. hand out a temporary download URL.
12
+
13
+ `StoredFileServiceMixin` does this flow **once**, parameterized by the
14
+ **field name** — so a single service can manage several file fields without
15
+ duplication. It builds on top of [`UploadUtils`](uploads.md) (upload +
16
+ validation) and [`AsyncMinIOClient`](storage.md) (presigned URL); it needs the
17
+ `[upload]` and `[minio]` extras.
18
+
19
+ !!! info "Scope: the common case"
20
+ Covers **one key per field → presigned URL**. Thumbnails, variants
21
+ (S/M/L), public/CDN buckets and galleries (one-to-many) are out of scope —
22
+ compose `UploadUtils` directly for those.
23
+
24
+ ## Mixing it into your service
25
+
26
+ The mixin builds nothing: it reads two collaborators off `self` —
27
+ `upload_utils` and `storage`. The service stays in charge of configuration
28
+ (size, allowed types, bucket):
29
+
30
+ ```python
31
+ from tempest_fastapi_sdk import (
32
+ AsyncMinIOClient,
33
+ BaseService,
34
+ StoredFileServiceMixin,
35
+ UploadUtils,
36
+ )
37
+
38
+ from src.db.models import UserModel
39
+ from src.db.repositories import UserRepository
40
+ from src.schemas import UserResponseSchema
41
+
42
+
43
+ class UserService(
44
+ BaseService[UserRepository, UserResponseSchema],
45
+ StoredFileServiceMixin[UserModel],
46
+ ):
47
+ def __init__(
48
+ self,
49
+ repository: UserRepository,
50
+ storage: AsyncMinIOClient,
51
+ upload_utils: UploadUtils,
52
+ ) -> None:
53
+ super().__init__(repository)
54
+ self.storage = storage
55
+ self.upload_utils = upload_utils
56
+ ```
57
+
58
+ Base order matters: `BaseService` brings the `repository`; the mixin only
59
+ adds the file methods on top.
60
+
61
+ ## Swap the file — `set_file`
62
+
63
+ ```python
64
+ async def update_profile_picture(
65
+ self, user: UUID | UserModel, image: UploadFile
66
+ ) -> UserResponseSchema:
67
+ """Upload the new picture, delete the old one, return the profile + URL."""
68
+ updated = await self.set_file(
69
+ user, image, field="profile_picture", subdir="profiles"
70
+ )
71
+ response = await self._map_to_response(updated)
72
+ response.profile_picture_url = await self.file_url(updated.profile_picture)
73
+ return response
74
+ ```
75
+
76
+ That's it. Against the ~13 hand-written lines, `set_file` resolves the
77
+ entity, calls `replace` (writes the new file **before** deleting the old),
78
+ stores the key and commits — in one step.
79
+
80
+ !!! tip "Safe with the authenticated user"
81
+ `set_file` re-resolves the entity on the request session via
82
+ `repository.resolve()`. If you pass the `UserModel` from
83
+ `get_current_user` (which on mis-wired apps used to be *detached*),
84
+ `resolve` re-attaches it before the write — no
85
+ `InvalidRequestError: Instance is not persistent within this Session`.
86
+
87
+ ## Serve the URL — `file_url`
88
+
89
+ ```python
90
+ url = await self.file_url(user.profile_picture) # 1h lifetime
91
+ url = await self.file_url(user.profile_picture, expires=timedelta(minutes=5))
92
+ ```
93
+
94
+ Returns `None` when the key is empty, so you can feed the result straight into
95
+ a response-schema field without an `if`:
96
+
97
+ ```python
98
+ response.profile_picture_url = await self.file_url(updated.profile_picture)
99
+ ```
100
+
101
+ ## Remove the file — `clear_file`
102
+
103
+ ```python
104
+ updated = await self.clear_file(user, field="profile_picture")
105
+ ```
106
+
107
+ Deletes the storage object and nulls the field. When the field is already
108
+ empty it is a **no-op**: the entity is returned without a `commit` and without
109
+ touching storage.
110
+
111
+ ## Several fields? Same mixin
112
+
113
+ `field=` is just an argument — one service handles as many fields as you like:
114
+
115
+ ```python
116
+ await self.set_file(event, cover, field="cover_image", subdir="events/covers")
117
+ await self.set_file(event, banner, field="banner_image", subdir="events/banners")
118
+ ```
119
+
120
+ ## Recap
121
+
122
+ - Mix `StoredFileServiceMixin[Model]` into the service and expose
123
+ `upload_utils` + `storage`.
124
+ - `set_file(ref, file, *, field, subdir=...)` → upload, swap old, persist.
125
+ Detach-safe.
126
+ - `file_url(key, *, expires=...)` → presigned URL or `None`.
127
+ - `clear_file(ref, *, field)` → delete + null (no-op when already empty).
128
+ - Common case (one key + presigned). For resize/variants/gallery, use
129
+ [`UploadUtils`](uploads.md) directly.
@@ -0,0 +1,128 @@
1
+ # Arquivo no serviço — `StoredFileServiceMixin`
2
+
3
+ Uma entidade quase sempre carrega **uma** chave de storage: o avatar do
4
+ usuário, o banner de um evento, a capa de um produto, um anexo. E todo
5
+ serviço que cuida disso reescreve a mesma dança na mão:
6
+
7
+ 1. resolver a entidade,
8
+ 2. subir o arquivo novo e apagar o antigo,
9
+ 3. gravar a chave nova no model,
10
+ 4. dar `commit`,
11
+ 5. devolver uma URL temporária de download.
12
+
13
+ O `StoredFileServiceMixin` faz esse fluxo **uma vez**, parametrizado pelo
14
+ **nome do campo** — então um mesmo serviço cuida de vários campos de arquivo
15
+ sem repetição. Ele monta por cima do [`UploadUtils`](uploads.md) (upload +
16
+ validação) e do [`AsyncMinIOClient`](storage.md) (presigned URL); requer os
17
+ extras `[upload]` e `[minio]`.
18
+
19
+ !!! info "Escopo: o caso comum"
20
+ Cobre **uma chave por campo → URL presigned**. Thumbnails, variantes
21
+ (S/M/L), bucket público/CDN e galerias (um-para-muitos) ficam de fora —
22
+ pra esses, componha o `UploadUtils` direto.
23
+
24
+ ## Misturando no seu serviço
25
+
26
+ O mixin não constrói nada: ele lê dois colaboradores de `self` —
27
+ `upload_utils` e `storage`. Quem mistura continua dono da configuração
28
+ (tamanho, tipos aceitos, bucket):
29
+
30
+ ```python
31
+ from tempest_fastapi_sdk import (
32
+ AsyncMinIOClient,
33
+ BaseService,
34
+ StoredFileServiceMixin,
35
+ UploadUtils,
36
+ )
37
+
38
+ from src.db.models import UserModel
39
+ from src.db.repositories import UserRepository
40
+ from src.schemas import UserResponseSchema
41
+
42
+
43
+ class UserService(
44
+ BaseService[UserRepository, UserResponseSchema],
45
+ StoredFileServiceMixin[UserModel],
46
+ ):
47
+ def __init__(
48
+ self,
49
+ repository: UserRepository,
50
+ storage: AsyncMinIOClient,
51
+ upload_utils: UploadUtils,
52
+ ) -> None:
53
+ super().__init__(repository)
54
+ self.storage = storage
55
+ self.upload_utils = upload_utils
56
+ ```
57
+
58
+ A ordem das bases importa: `BaseService` traz o `repository`; o mixin só
59
+ adiciona os métodos de arquivo por cima.
60
+
61
+ ## Trocar o arquivo — `set_file`
62
+
63
+ ```python
64
+ async def update_profile_picture(
65
+ self, user: UUID | UserModel, image: UploadFile
66
+ ) -> UserResponseSchema:
67
+ """Sobe a foto nova, apaga a antiga e devolve o perfil com a URL."""
68
+ updated = await self.set_file(
69
+ user, image, field="profile_picture", subdir="profiles"
70
+ )
71
+ response = await self._map_to_response(updated)
72
+ response.profile_picture_url = await self.file_url(updated.profile_picture)
73
+ return response
74
+ ```
75
+
76
+ Foi isso. Comparado às ~13 linhas manuais, o `set_file` resolve a entidade,
77
+ chama `replace` (grava a nova **antes** de apagar a antiga), grava a chave e
78
+ dá `commit` — tudo num passo.
79
+
80
+ !!! tip "Seguro com o usuário autenticado"
81
+ O `set_file` re-resolve a entidade na sessão do request via
82
+ `repository.resolve()`. Se você passar o `UserModel` que veio de
83
+ `get_current_user` (que em apps mal-fiados vinha *detached*), o `resolve`
84
+ o reanexa antes da escrita — sem o
85
+ `InvalidRequestError: Instance is not persistent within this Session`.
86
+
87
+ ## Servir a URL — `file_url`
88
+
89
+ ```python
90
+ url = await self.file_url(user.profile_picture) # 1h de validade
91
+ url = await self.file_url(user.profile_picture, expires=timedelta(minutes=5))
92
+ ```
93
+
94
+ Devolve `None` quando a chave é vazia, então você pode jogar o resultado
95
+ direto num campo do schema de resposta sem `if`:
96
+
97
+ ```python
98
+ response.profile_picture_url = await self.file_url(updated.profile_picture)
99
+ ```
100
+
101
+ ## Remover o arquivo — `clear_file`
102
+
103
+ ```python
104
+ updated = await self.clear_file(user, field="profile_picture")
105
+ ```
106
+
107
+ Apaga o objeto do storage e zera o campo. Quando o campo já está vazio, é
108
+ um **no-op**: a entidade volta sem `commit` e sem chamar o storage.
109
+
110
+ ## Vários campos? Mesmo mixin
111
+
112
+ `field=` é só um argumento — o mesmo serviço cuida de quantos campos quiser:
113
+
114
+ ```python
115
+ await self.set_file(event, cover, field="cover_image", subdir="events/covers")
116
+ await self.set_file(event, banner, field="banner_image", subdir="events/banners")
117
+ ```
118
+
119
+ ## Recapitulando
120
+
121
+ - Misture `StoredFileServiceMixin[Model]` no serviço e exponha `upload_utils`
122
+ + `storage`.
123
+ - `set_file(ref, file, *, field, subdir=...)` → sobe, troca a antiga,
124
+ persiste. Detach-safe.
125
+ - `file_url(key, *, expires=...)` → URL presigned ou `None`.
126
+ - `clear_file(ref, *, field)` → apaga + zera (no-op se já vazio).
127
+ - Caso comum (uma chave + presigned). Pra resize/variantes/galeria, use o
128
+ [`UploadUtils`](uploads.md) direto.
@@ -68,6 +68,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
68
68
  ## Services & Controllers
69
69
 
70
70
  ::: tempest_fastapi_sdk.services.base.BaseService
71
+ ::: tempest_fastapi_sdk.services.file_mixin.StoredFileServiceMixin
71
72
  ::: tempest_fastapi_sdk.controllers.base.BaseController
72
73
 
73
74
  ---
@@ -220,6 +220,7 @@ nav:
220
220
  - "Observabilidade (tracing)": recipes/observability.md
221
221
  - Storage (MinIO/S3): recipes/storage.md
222
222
  - Uploads (backends): recipes/uploads.md
223
+ - "Arquivo no serviço (mixin)": recipes/stored-files.md
223
224
  - Downloads: recipes/downloads.md
224
225
  - Idempotência: recipes/idempotency.md
225
226
  - Sync offline-first (delta): recipes/offline-sync.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tempest-fastapi-sdk"
3
- version = "0.63.0"
3
+ version = "0.64.0"
4
4
  description = "Shared FastAPI building blocks: base schemas, ORM model, async repository, exceptions, pagination and settings — the conventions used across Tempest projects."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -180,7 +180,12 @@ from tempest_fastapi_sdk.schemas import (
180
180
  decode_cursor,
181
181
  encode_cursor,
182
182
  )
183
- from tempest_fastapi_sdk.services import BaseService
183
+ from tempest_fastapi_sdk.services import (
184
+ BaseService,
185
+ StoredFileServiceMixin,
186
+ SupportsPresign,
187
+ SupportsUpload,
188
+ )
184
189
  from tempest_fastapi_sdk.sessions import (
185
190
  MemorySessionStore,
186
191
  RedisSessionStore,
@@ -301,7 +306,7 @@ from tempest_fastapi_sdk.websockets import (
301
306
  make_websocket_router,
302
307
  )
303
308
 
304
- __version__: str = "0.63.0"
309
+ __version__: str = "0.64.0"
305
310
 
306
311
  __all__: list[str] = [
307
312
  "BASE_COLUMN_ORDER",
@@ -462,6 +467,9 @@ __all__: list[str] = [
462
467
  "SlowQueryLogger",
463
468
  "SoftDeleteMixin",
464
469
  "StateBR",
470
+ "StoredFileServiceMixin",
471
+ "SupportsPresign",
472
+ "SupportsUpload",
465
473
  "SyncFilterSchema",
466
474
  "SyncPaginationSchema",
467
475
  "SystemMetrics",
@@ -0,0 +1,15 @@
1
+ """Business-logic layer base classes."""
2
+
3
+ from tempest_fastapi_sdk.services.base import BaseService
4
+ from tempest_fastapi_sdk.services.file_mixin import (
5
+ StoredFileServiceMixin,
6
+ SupportsPresign,
7
+ SupportsUpload,
8
+ )
9
+
10
+ __all__: list[str] = [
11
+ "BaseService",
12
+ "StoredFileServiceMixin",
13
+ "SupportsPresign",
14
+ "SupportsUpload",
15
+ ]
@@ -0,0 +1,221 @@
1
+ """Reusable service mixin for single-key stored-file fields.
2
+
3
+ A great many entities carry exactly one object-storage key — an avatar,
4
+ a banner, a cover, a document — and every service that owns one rewrites
5
+ the same orchestration by hand:
6
+
7
+ 1. resolve the entity (detach-safe),
8
+ 2. upload the new file and delete the one it replaces,
9
+ 3. write the new key back onto the model,
10
+ 4. commit, and
11
+ 5. hand out a presigned download URL.
12
+
13
+ :class:`StoredFileServiceMixin` encodes that flow once, parameterized by
14
+ the **field name**, so a single service can manage several file fields
15
+ without duplication. It deliberately covers only the common
16
+ "one key column → presigned URL" case; resize/thumbnail pipelines,
17
+ multi-variant assets and gallery (one-to-many) uploads are out of scope —
18
+ compose :class:`UploadUtils` directly for those.
19
+
20
+ The mixin is storage-agnostic by structural typing: it reads two
21
+ collaborators off ``self`` — ``upload_utils`` and ``storage`` — described
22
+ by the :class:`SupportsUpload` and :class:`SupportsPresign` protocols, so
23
+ importing it never pulls the optional ``[upload]`` / ``[minio]`` extras.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from datetime import timedelta
29
+ from pathlib import Path
30
+ from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar
31
+ from uuid import UUID
32
+
33
+ from tempest_fastapi_sdk.db.model import BaseModel
34
+
35
+ if TYPE_CHECKING:
36
+ from fastapi import UploadFile
37
+
38
+ from tempest_fastapi_sdk.db.repository import BaseRepository
39
+
40
+ ModelType = TypeVar("ModelType", bound=BaseModel)
41
+
42
+ # One hour matches AsyncMinIOClient.presigned_get_url's own default.
43
+ _DEFAULT_URL_TTL = timedelta(hours=1)
44
+
45
+
46
+ class SupportsUpload(Protocol):
47
+ """Structural type for the upload helper the mixin needs.
48
+
49
+ Satisfied by :class:`tempest_fastapi_sdk.UploadUtils`.
50
+ """
51
+
52
+ async def replace(
53
+ self,
54
+ old_key: Path | str | None,
55
+ file: UploadFile,
56
+ *,
57
+ subdir: str = ...,
58
+ filename: str | None = ...,
59
+ keep_original_name: bool = ...,
60
+ ) -> Path:
61
+ """Persist ``file`` and delete the object at ``old_key``."""
62
+ ...
63
+
64
+ async def delete(self, key: Path | str) -> bool:
65
+ """Delete the object at ``key``; return whether it existed."""
66
+ ...
67
+
68
+
69
+ class SupportsPresign(Protocol):
70
+ """Structural type for the storage client the mixin needs.
71
+
72
+ Satisfied by :class:`tempest_fastapi_sdk.AsyncMinIOClient`.
73
+ """
74
+
75
+ async def presigned_get_url(
76
+ self,
77
+ key: str,
78
+ *,
79
+ expires: timedelta = ...,
80
+ ) -> str:
81
+ """Return a temporary download URL for ``key``."""
82
+ ...
83
+
84
+
85
+ class StoredFileServiceMixin(Generic[ModelType]):
86
+ """Manage a single object-storage key field on a service's model.
87
+
88
+ Mix into a service that already owns a ``repository`` (e.g. a
89
+ :class:`tempest_fastapi_sdk.BaseService` subclass) and exposes an
90
+ ``upload_utils`` and a ``storage`` collaborator::
91
+
92
+ class UserService(
93
+ BaseService[UserRepository, UserResponseSchema],
94
+ StoredFileServiceMixin[UserModel],
95
+ ):
96
+ def __init__(self, repository, storage, upload_utils):
97
+ super().__init__(repository)
98
+ self.storage = storage
99
+ self.upload_utils = upload_utils
100
+
101
+ user = await service.set_file(
102
+ user, image, field="profile_picture", subdir="profiles"
103
+ )
104
+ url = await service.file_url(user.profile_picture)
105
+
106
+ The mixin does not construct its collaborators — it reads them off
107
+ ``self`` so the owning service stays in charge of configuration
108
+ (size limits, allowed types, bucket).
109
+
110
+ Generic parameters:
111
+ ModelType: The ORM model whose field is managed.
112
+
113
+ Attributes:
114
+ repository (BaseRepository[ModelType]): Supplied by the service.
115
+ upload_utils (SupportsUpload): Supplied by the service.
116
+ storage (SupportsPresign): Supplied by the service.
117
+ """
118
+
119
+ repository: BaseRepository[ModelType]
120
+ upload_utils: SupportsUpload
121
+ storage: SupportsPresign
122
+
123
+ async def set_file(
124
+ self,
125
+ ref: UUID | ModelType,
126
+ file: UploadFile,
127
+ *,
128
+ field: str,
129
+ subdir: str = "",
130
+ filename: str | None = None,
131
+ keep_original_name: bool = False,
132
+ ) -> ModelType:
133
+ """Upload ``file`` and store its key on ``field``, replacing any old one.
134
+
135
+ The new object is written **before** the old one is deleted (via
136
+ :meth:`SupportsUpload.replace`), so a failed upload leaves the
137
+ existing file untouched. The entity is re-resolved on the
138
+ repository's session first, so a detached instance (e.g. the
139
+ authenticated user) is safely re-attached before the write.
140
+
141
+ Args:
142
+ ref (UUID | ModelType): The entity to update, by id or instance.
143
+ file (UploadFile): The uploaded file.
144
+ field (str): Name of the model attribute holding the storage key.
145
+ subdir (str): Optional key prefix / sub-directory.
146
+ filename (str | None): Explicit final filename. When omitted the
147
+ upload helper derives one.
148
+ keep_original_name (bool): Preserve the upload's original
149
+ filename when ``filename`` is not given.
150
+
151
+ Returns:
152
+ ModelType: The persisted entity with the new key on ``field``.
153
+
154
+ Raises:
155
+ AppException: ``repository.not_found_exception`` when ``ref`` is
156
+ an id with no matching row.
157
+ """
158
+ entity = await self.repository.resolve(ref)
159
+ old_key: Any = getattr(entity, field)
160
+ new_key = await self.upload_utils.replace(
161
+ old_key,
162
+ file,
163
+ subdir=subdir,
164
+ filename=filename,
165
+ keep_original_name=keep_original_name,
166
+ )
167
+ setattr(entity, field, str(new_key))
168
+ return await self.repository.update(entity)
169
+
170
+ async def clear_file(
171
+ self,
172
+ ref: UUID | ModelType,
173
+ *,
174
+ field: str,
175
+ ) -> ModelType:
176
+ """Delete the stored object (if any) and null ``field``.
177
+
178
+ A no-op write is avoided when the field is already empty: the
179
+ resolved entity is returned without a commit.
180
+
181
+ Args:
182
+ ref (UUID | ModelType): The entity to update, by id or instance.
183
+ field (str): Name of the model attribute holding the storage key.
184
+
185
+ Returns:
186
+ ModelType: The entity with ``field`` set to ``None`` (persisted
187
+ only when there was a key to clear).
188
+
189
+ Raises:
190
+ AppException: ``repository.not_found_exception`` when ``ref`` is
191
+ an id with no matching row.
192
+ """
193
+ entity = await self.repository.resolve(ref)
194
+ old_key: Any = getattr(entity, field)
195
+ if not old_key:
196
+ return entity
197
+ await self.upload_utils.delete(old_key)
198
+ setattr(entity, field, None)
199
+ return await self.repository.update(entity)
200
+
201
+ async def file_url(
202
+ self,
203
+ key: str | None,
204
+ *,
205
+ expires: timedelta = _DEFAULT_URL_TTL,
206
+ ) -> str | None:
207
+ """Return a presigned download URL for ``key``, or ``None`` when empty.
208
+
209
+ Args:
210
+ key (str | None): The stored object key (typically the field
211
+ value). ``None`` / empty returns ``None`` so callers can
212
+ forward the result straight into a response schema.
213
+ expires (timedelta): URL lifetime. Defaults to one hour.
214
+
215
+ Returns:
216
+ str | None: A temporary download URL, or ``None`` when there is
217
+ no key.
218
+ """
219
+ if not key:
220
+ return None
221
+ return await self.storage.presigned_get_url(key, expires=expires)