tempest-fastapi-sdk 0.44.0__tar.gz → 0.46.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 (388) hide show
  1. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/CHANGELOG.md +50 -0
  2. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/PKG-INFO +3 -3
  3. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/README.md +2 -2
  4. tempest_fastapi_sdk-0.46.0/docs/recipes/deploy-safety.en.md +111 -0
  5. tempest_fastapi_sdk-0.46.0/docs/recipes/deploy-safety.md +110 -0
  6. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/index.en.md +2 -0
  7. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/index.md +2 -0
  8. tempest_fastapi_sdk-0.46.0/docs/recipes/multi-tenant.en.md +120 -0
  9. tempest_fastapi_sdk-0.46.0/docs/recipes/multi-tenant.md +120 -0
  10. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/reference.md +3 -0
  11. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/mkdocs.yml +4 -0
  12. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/pyproject.toml +1 -1
  13. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/__init__.py +7 -1
  14. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/__init__.py +2 -0
  15. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +2 -0
  16. tempest_fastapi_sdk-0.46.0/tempest_fastapi_sdk/api/middlewares/graceful.py +225 -0
  17. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/__init__.py +8 -0
  18. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/migrations.py +116 -0
  19. tempest_fastapi_sdk-0.46.0/tempest_fastapi_sdk/db/tenant.py +348 -0
  20. tempest_fastapi_sdk-0.46.0/tests/api/test_graceful.py +78 -0
  21. tempest_fastapi_sdk-0.46.0/tests/db/test_safe_upgrade.py +98 -0
  22. tempest_fastapi_sdk-0.46.0/tests/db/test_tenant.py +170 -0
  23. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/uv.lock +1 -1
  24. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/.github/workflows/ci.yml +0 -0
  25. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/.github/workflows/docs.yml +0 -0
  26. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/.github/workflows/release-pypi.yml +0 -0
  27. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/.gitignore +0 -0
  28. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/.python-version +0 -0
  29. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/CLAUDE.md +0 -0
  30. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/Makefile +0 -0
  31. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/architecture.en.md +0 -0
  32. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/architecture.md +0 -0
  33. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/changelog.en.md +0 -0
  34. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/changelog.md +0 -0
  35. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/contributing.en.md +0 -0
  36. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/contributing.md +0 -0
  37. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/index.en.md +0 -0
  38. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/index.md +0 -0
  39. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/installation.en.md +0 -0
  40. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/installation.md +0 -0
  41. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/index.en.md +0 -0
  42. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/index.md +0 -0
  43. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/api.en.md +0 -0
  44. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/api.md +0 -0
  45. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/business-rules.en.md +0 -0
  46. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/business-rules.md +0 -0
  47. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/domain.en.md +0 -0
  48. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/domain.md +0 -0
  49. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/flows.en.md +0 -0
  50. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/flows.md +0 -0
  51. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/index.en.md +0 -0
  52. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/learning/marketplace/index.md +0 -0
  53. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/migration.en.md +0 -0
  54. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/migration.md +0 -0
  55. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/admin.en.md +0 -0
  56. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/admin.md +0 -0
  57. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/auth-flow.en.md +0 -0
  58. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/auth-flow.md +0 -0
  59. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/br-helpers.en.md +0 -0
  60. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/br-helpers.md +0 -0
  61. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/cache.en.md +0 -0
  62. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/cache.md +0 -0
  63. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/cli.en.md +0 -0
  64. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/cli.md +0 -0
  65. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/database.en.md +0 -0
  66. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/database.md +0 -0
  67. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/downloads.en.md +0 -0
  68. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/downloads.md +0 -0
  69. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/email.en.md +0 -0
  70. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/email.md +0 -0
  71. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/http-client.en.md +0 -0
  72. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/http-client.md +0 -0
  73. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/http.en.md +0 -0
  74. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/http.md +0 -0
  75. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/idempotency.en.md +0 -0
  76. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/idempotency.md +0 -0
  77. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/logging.en.md +0 -0
  78. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/logging.md +0 -0
  79. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/metrics.en.md +0 -0
  80. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/metrics.md +0 -0
  81. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/mfa.en.md +0 -0
  82. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/mfa.md +0 -0
  83. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/observability.en.md +0 -0
  84. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/observability.md +0 -0
  85. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/outbox.en.md +0 -0
  86. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/outbox.md +0 -0
  87. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/queue-tasks.en.md +0 -0
  88. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/queue-tasks.md +0 -0
  89. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/realtime.en.md +0 -0
  90. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/realtime.md +0 -0
  91. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/security.en.md +0 -0
  92. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/security.md +0 -0
  93. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/sessions.en.md +0 -0
  94. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/sessions.md +0 -0
  95. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/storage.en.md +0 -0
  96. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/storage.md +0 -0
  97. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/testing.en.md +0 -0
  98. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/testing.md +0 -0
  99. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/uploads.en.md +0 -0
  100. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/uploads.md +0 -0
  101. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/utilities.en.md +0 -0
  102. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/utilities.md +0 -0
  103. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/webpush.en.md +0 -0
  104. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/webpush.md +0 -0
  105. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/websocket.en.md +0 -0
  106. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/recipes/websocket.md +0 -0
  107. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/reference.en.md +0 -0
  108. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/roadmap.en.md +0 -0
  109. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/roadmap.md +0 -0
  110. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/tutorial.en.md +0 -0
  111. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/docs/tutorial.md +0 -0
  112. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/scripts/extract_recipe.py +0 -0
  113. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
  114. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
  115. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/config.py +0 -0
  116. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
  117. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/router.py +0 -0
  118. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/session.py +0 -0
  119. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/site.py +0 -0
  120. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
  121. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
  122. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
  123. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
  124. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
  125. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
  126. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
  127. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
  128. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
  129. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
  130. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
  131. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
  132. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
  133. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
  134. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
  135. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
  136. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
  137. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
  138. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
  139. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
  140. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
  141. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
  142. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
  143. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
  144. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
  145. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/server.py +0 -0
  146. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/static.py +0 -0
  147. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
  148. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
  149. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
  150. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
  151. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/router.py +0 -0
  152. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
  153. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/service.py +0 -0
  154. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/activation.html +0 -0
  155. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/activation_error.html +0 -0
  156. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/activation_success.html +0 -0
  157. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/password_reset.html +0 -0
  158. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/password_reset_error.html +0 -0
  159. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/password_reset_form.html +0 -0
  160. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/auth/templates/password_reset_success.html +0 -0
  161. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
  162. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
  163. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
  164. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
  165. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
  166. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
  167. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
  168. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
  169. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
  170. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
  171. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
  172. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
  173. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
  174. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
  175. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
  176. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
  177. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
  178. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
  179. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
  180. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
  181. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
  182. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
  183. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
  184. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
  185. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
  186. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
  187. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
  188. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
  189. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
  190. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
  191. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/db.py +0 -0
  192. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
  193. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
  194. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
  195. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/main.py +0 -0
  196. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/new.py +0 -0
  197. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
  198. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/cli/user.py +0 -0
  199. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
  200. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
  201. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
  202. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/core/context.py +0 -0
  203. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/core/enums.py +0 -0
  204. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/core/logging.py +0 -0
  205. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
  206. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
  207. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
  208. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/connection.py +0 -0
  209. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
  210. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/model.py +0 -0
  211. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
  212. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/repository.py +0 -0
  213. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
  214. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
  215. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
  216. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
  217. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
  218. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
  219. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
  220. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
  221. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
  222. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
  223. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
  224. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
  225. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
  226. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
  227. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/py.typed +0 -0
  228. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
  229. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
  230. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
  231. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
  232. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
  233. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
  234. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
  235. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
  236. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
  237. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/services/base.py +0 -0
  238. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
  239. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
  240. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
  241. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
  242. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
  243. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
  244. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
  245. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
  246. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/settings/base.py +0 -0
  247. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
  248. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
  249. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
  250. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
  251. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
  252. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
  253. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
  254. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
  255. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
  256. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/testing/database.py +0 -0
  257. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
  258. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
  259. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
  260. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
  261. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/download.py +0 -0
  262. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/email.py +0 -0
  263. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
  264. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
  265. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/log.py +0 -0
  266. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
  267. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
  268. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/password.py +0 -0
  269. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
  270. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
  271. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
  272. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
  273. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
  274. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
  275. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
  276. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
  277. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
  278. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
  279. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
  280. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
  281. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/__init__.py +0 -0
  282. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/__init__.py +0 -0
  283. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_auth.py +0 -0
  284. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_forms.py +0 -0
  285. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_logs_nav.py +0 -0
  286. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_mfa.py +0 -0
  287. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_router.py +0 -0
  288. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_site.py +0 -0
  289. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/admin/test_user_model.py +0 -0
  290. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/__init__.py +0 -0
  291. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_body_size.py +0 -0
  292. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_cookies.py +0 -0
  293. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_cors.py +0 -0
  294. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_csrf.py +0 -0
  295. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_dependencies_auth.py +0 -0
  296. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_handlers.py +0 -0
  297. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_health_router.py +0 -0
  298. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_idempotency.py +0 -0
  299. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_jwt_dependency.py +0 -0
  300. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_logs_router.py +0 -0
  301. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_oauth.py +0 -0
  302. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_prometheus.py +0 -0
  303. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_rate_limit.py +0 -0
  304. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_request_id_middleware.py +0 -0
  305. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_role_dependency.py +0 -0
  306. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_server.py +0 -0
  307. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_static.py +0 -0
  308. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_tool_spec.py +0 -0
  309. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_tracing.py +0 -0
  310. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_webhooks.py +0 -0
  311. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/api/test_webhooks_rsa.py +0 -0
  312. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/auth/__init__.py +0 -0
  313. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/auth/test_mfa.py +0 -0
  314. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/auth/test_service.py +0 -0
  315. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cache/__init__.py +0 -0
  316. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cache/test_decorator.py +0 -0
  317. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cache/test_redis_manager.py +0 -0
  318. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cli/__init__.py +0 -0
  319. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cli/test_db.py +0 -0
  320. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cli/test_docker_compose.py +0 -0
  321. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cli/test_generate.py +0 -0
  322. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cli/test_main.py +0 -0
  323. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/cli/test_user.py +0 -0
  324. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/conftest.py +0 -0
  325. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/controllers/__init__.py +0 -0
  326. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/controllers/test_base.py +0 -0
  327. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/core/__init__.py +0 -0
  328. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/core/test_context.py +0 -0
  329. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/core/test_enums.py +0 -0
  330. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/core/test_logging.py +0 -0
  331. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/__init__.py +0 -0
  332. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_alembic_hooks.py +0 -0
  333. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_bulk_ops.py +0 -0
  334. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_connection.py +0 -0
  335. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_migrations.py +0 -0
  336. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_mixins.py +0 -0
  337. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_model.py +0 -0
  338. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_outbox.py +0 -0
  339. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_repository.py +0 -0
  340. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/db/test_slow_query.py +0 -0
  341. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/exceptions/__init__.py +0 -0
  342. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/exceptions/test_exceptions.py +0 -0
  343. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/queue/__init__.py +0 -0
  344. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/queue/test_manager.py +0 -0
  345. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/schemas/__init__.py +0 -0
  346. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/schemas/test_base.py +0 -0
  347. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/schemas/test_cursor_pagination.py +0 -0
  348. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/schemas/test_link_headers.py +0 -0
  349. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/schemas/test_pagination.py +0 -0
  350. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/schemas/test_response.py +0 -0
  351. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/services/__init__.py +0 -0
  352. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/services/test_base.py +0 -0
  353. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/sessions/__init__.py +0 -0
  354. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/sessions/test_sessions.py +0 -0
  355. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/settings/__init__.py +0 -0
  356. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/settings/test_base.py +0 -0
  357. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/settings/test_mixins.py +0 -0
  358. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/sse/__init__.py +0 -0
  359. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/sse/test_event_stream.py +0 -0
  360. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/storage/__init__.py +0 -0
  361. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/storage/test_minio_client.py +0 -0
  362. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/tasks/__init__.py +0 -0
  363. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/tasks/test_manager.py +0 -0
  364. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/tasks/test_scheduler.py +0 -0
  365. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/testing/__init__.py +0 -0
  366. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/testing/test_database.py +0 -0
  367. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/__init__.py +0 -0
  368. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_client_ip.py +0 -0
  369. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_datetime.py +0 -0
  370. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_dict.py +0 -0
  371. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_download.py +0 -0
  372. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_email.py +0 -0
  373. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_http_client.py +0 -0
  374. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_jwt.py +0 -0
  375. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_lazy_extras.py +0 -0
  376. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_log.py +0 -0
  377. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_metrics.py +0 -0
  378. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_opaque_token.py +0 -0
  379. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_password.py +0 -0
  380. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_regex.py +0 -0
  381. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_storage_backends.py +0 -0
  382. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_throttle.py +0 -0
  383. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/utils/test_upload.py +0 -0
  384. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/webpush/__init__.py +0 -0
  385. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/webpush/test_dispatcher.py +0 -0
  386. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/webpush/test_schemas.py +0 -0
  387. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/websockets/__init__.py +0 -0
  388. {tempest_fastapi_sdk-0.44.0 → tempest_fastapi_sdk-0.46.0}/tests/websockets/test_hub_and_router.py +0 -0
@@ -5,6 +5,56 @@ 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.46.0] — 2026-06-11
9
+
10
+ ### Added
11
+
12
+ - **`AlembicHelper.safe_upgrade(revision="head", *, force=False)`** —
13
+ runs the upgrade only after scanning each pending migration's
14
+ `upgrade()` for data-destroying calls (`op.drop_table` /
15
+ `op.drop_column` / `op.drop_constraint` and `batch_op` variants). When
16
+ any are found it raises **`DestructiveMigrationError`** (carrying the
17
+ offending `(revision, operation)` pairs) and leaves the database
18
+ untouched; `force=True` logs and proceeds. Source-based scanning is
19
+ dialect-agnostic (no false positives on SQLite batch rebuilds) and
20
+ ignores drops in `downgrade()`. `pending_destructive_ops()` exposes the
21
+ scan without running anything (CI-friendly).
22
+ - **`GracefulShutdownMiddleware`** — tracks in-flight requests and, once
23
+ draining, replies `503` + `Retry-After` to new requests so a load
24
+ balancer deregisters the instance. `begin_drain()` / `wait_drained()`
25
+ (bounded by `drain_timeout`) are driven from the lifespan shutdown
26
+ (uvicorn owns `SIGTERM`); an opt-in `install_signal_handlers()` chains
27
+ the previous handler for servers that manage signals themselves. Wired
28
+ via `app.add_middleware(BaseHTTPMiddleware, dispatch=shutdown.dispatch)`.
29
+
30
+ ### Docs
31
+
32
+ - New bilingual recipe **Deploy seguro / Safe deploys** covering
33
+ `safe_upgrade` and `GracefulShutdownMiddleware`, added to the nav, the
34
+ recipes index, and the API reference.
35
+
36
+ ## [0.45.0] — 2026-06-11
37
+
38
+ ### Added
39
+
40
+ - **`TenantScopedRepository[ModelType]`** — a `BaseRepository` locked
41
+ to a single tenant for shared-schema multi-tenancy. Bind a
42
+ `tenant_id` (and optional `tenant_field`, default `"tenant_id"`) at
43
+ construction; it injects `WHERE tenant_id = ?` into **every** read
44
+ (`get`/`get_or_none`/`get_by_id`/`exists`/`first`/`list`/`count`/
45
+ `paginate`/`cursor_paginate`/`delete_many`) and stamps the tenant id
46
+ onto **every** write (`add`/`add_all`). `delete` / `delete_batch` add
47
+ the tenant predicate to the `DELETE` so a guessed id from another
48
+ tenant matches nothing — cross-tenant access (even probing existence
49
+ by id) is impossible through the repository. The constructor raises
50
+ `AttributeError` at boot if the model lacks the tenant column.
51
+ `tenant_column` property exposes the mapped column for custom queries.
52
+
53
+ ### Docs
54
+
55
+ - New bilingual recipe **Multi-tenant**, added to the nav (under
56
+ Database), the recipes index, and the API reference.
57
+
8
58
  ## [0.44.0] — 2026-06-11
9
59
 
10
60
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tempest-fastapi-sdk
3
- Version: 0.44.0
3
+ Version: 0.46.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
@@ -238,10 +238,10 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
238
238
  | Module | Exports |
239
239
  | --- | --- |
240
240
  | `tempest_fastapi_sdk.schemas` | `BaseSchema`, `BaseResponseSchema`, `BasePaginationFilterSchema`, `BasePaginationSchema[T]`, `CursorPaginationFilterSchema`, `CursorPaginationSchema`, `LogEntrySchema`, `encode_cursor`, `decode_cursor`, `build_pagination_link_header` |
241
- | `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
241
+ | `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `TenantScopedRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper` (+ `safe_upgrade`), `DestructiveMigrationError`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
242
242
  | `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException` |
243
243
  | `tempest_fastapi_sdk.settings` | `BaseAppSettings`, `ServerSettings`, `LogSettings`, `DatabaseSettings`, `RedisSettings`, `RabbitMQSettings`, `JWTSettings`, `CORSSettings`, `EmailSettings`, `UploadSettings`, `TokenSettings`, `WebPushSettings`, `TaskIQSettings`, `MinIOSettings`, `AuthSettings` |
244
- | `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
244
+ | `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `GracefulShutdownMiddleware`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
245
245
  | `tempest_fastapi_sdk.auth` *(extra: `[auth]`, opcional `[email]`, `[mfa]`)* | `UserAuthService`, `make_auth_router`, `SignupSchema` / `LoginSchema` / `PasswordResetRequestSchema` / `PasswordResetConfirmSchema` + responses, `ActivationToken`, `PasswordResetToken`, MFA schemas (`MFAEnrollResponseSchema` / `MFAConfirmSchema` / `MFAVerifySchema` / `MFADisableSchema`) — signup/activate/login/reset + TOTP 2FA out of the box |
246
246
  | `tempest_fastapi_sdk.controllers` | `BaseController` |
247
247
  | `tempest_fastapi_sdk.services` | `BaseService` |
@@ -136,10 +136,10 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
136
136
  | Module | Exports |
137
137
  | --- | --- |
138
138
  | `tempest_fastapi_sdk.schemas` | `BaseSchema`, `BaseResponseSchema`, `BasePaginationFilterSchema`, `BasePaginationSchema[T]`, `CursorPaginationFilterSchema`, `CursorPaginationSchema`, `LogEntrySchema`, `encode_cursor`, `decode_cursor`, `build_pagination_link_header` |
139
- | `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
139
+ | `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `TenantScopedRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper` (+ `safe_upgrade`), `DestructiveMigrationError`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
140
140
  | `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException` |
141
141
  | `tempest_fastapi_sdk.settings` | `BaseAppSettings`, `ServerSettings`, `LogSettings`, `DatabaseSettings`, `RedisSettings`, `RabbitMQSettings`, `JWTSettings`, `CORSSettings`, `EmailSettings`, `UploadSettings`, `TokenSettings`, `WebPushSettings`, `TaskIQSettings`, `MinIOSettings`, `AuthSettings` |
142
- | `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
142
+ | `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `GracefulShutdownMiddleware`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
143
143
  | `tempest_fastapi_sdk.auth` *(extra: `[auth]`, opcional `[email]`, `[mfa]`)* | `UserAuthService`, `make_auth_router`, `SignupSchema` / `LoginSchema` / `PasswordResetRequestSchema` / `PasswordResetConfirmSchema` + responses, `ActivationToken`, `PasswordResetToken`, MFA schemas (`MFAEnrollResponseSchema` / `MFAConfirmSchema` / `MFAVerifySchema` / `MFADisableSchema`) — signup/activate/login/reset + TOTP 2FA out of the box |
144
144
  | `tempest_fastapi_sdk.controllers` | `BaseController` |
145
145
  | `tempest_fastapi_sdk.services` | `BaseService` |
@@ -0,0 +1,111 @@
1
+ # Safe deploys (migrations + graceful shutdown)
2
+
3
+ Two classic deploy risks: a migration that **deletes data** by accident,
4
+ and a rollout that **severs in-flight requests** when the old pod dies.
5
+ This recipe covers the two defenses the SDK ships.
6
+
7
+ ## Safe migrations: `safe_upgrade`
8
+
9
+ `AlembicHelper.safe_upgrade()` runs the upgrade **only if** no pending
10
+ migration is destructive. It scans each pending revision's `def upgrade()`
11
+ for data-deleting calls — `op.drop_table`, `op.drop_column`,
12
+ `op.drop_constraint` (and `batch_op` variants) — and, if it finds one,
13
+ raises `DestructiveMigrationError` **without touching the database**.
14
+
15
+ ```python
16
+ from tempest_fastapi_sdk import AlembicHelper, DestructiveMigrationError
17
+
18
+
19
+ def deploy_migrations() -> None:
20
+ """Apply migrations on deploy, blocking accidental DROPs."""
21
+ helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
22
+ try:
23
+ helper.safe_upgrade("head")
24
+ except DestructiveMigrationError as exc:
25
+ # CI/CD fails here — someone must review and unblock with force.
26
+ for revision, op in exc.offences:
27
+ print(f"blocked: {revision} → {op}")
28
+ raise
29
+ ```
30
+
31
+ The scan looks at the migration **code**, not the generated SQL — so it
32
+ never false-positives on the table rebuild SQLite does in batch mode. A
33
+ `drop_*` in `downgrade()` (the normal, expected path) is ignored.
34
+
35
+ ### Allowing an intentional DROP
36
+
37
+ When the DROP is intentional (you took a backup, you reviewed it), pass
38
+ `force=True` — the destructive operations are logged and the upgrade runs:
39
+
40
+ ```python
41
+ from tempest_fastapi_sdk import AlembicHelper
42
+
43
+ helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
44
+ helper.safe_upgrade("head", force=True) # I know what I'm doing
45
+ ```
46
+
47
+ !!! tip "Inspect only"
48
+ `helper.pending_destructive_ops("head")` returns the list of
49
+ `(revision, operation)` without running anything — handy for a CI step
50
+ that only reports.
51
+
52
+ !!! danger "force=True deletes data"
53
+ `DROP COLUMN` / `DROP TABLE` are irreversible. Only use `force=True`
54
+ after a backup and human review.
55
+
56
+ ## Graceful shutdown: drain in-flight requests
57
+
58
+ On a rollout the orchestrator sends `SIGTERM` and, after a grace period,
59
+ `SIGKILL`. If a request is still running when the worker dies, it's
60
+ severed — an intermittent 502. `GracefulShutdownMiddleware`:
61
+
62
+ 1. Once **draining**, replies `503` + `Retry-After` to new requests, so
63
+ the load balancer stops routing to this pod.
64
+ 2. **Counts** in-flight requests; `wait_drained()` waits for them to
65
+ finish (with a timeout) before the process exits.
66
+
67
+ You hold the instance and drive draining from the `lifespan` (uvicorn runs
68
+ the lifespan shutdown on `SIGTERM` — and it owns the signal handling):
69
+
70
+ ```python
71
+ from contextlib import asynccontextmanager
72
+ from collections.abc import AsyncIterator
73
+
74
+ from fastapi import FastAPI
75
+ from starlette.middleware.base import BaseHTTPMiddleware
76
+
77
+ from tempest_fastapi_sdk import GracefulShutdownMiddleware
78
+
79
+ shutdown: GracefulShutdownMiddleware = GracefulShutdownMiddleware(drain_timeout=25.0)
80
+
81
+
82
+ @asynccontextmanager
83
+ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
84
+ """Drain in-flight requests on shutdown."""
85
+ yield
86
+ shutdown.begin_drain()
87
+ await shutdown.wait_drained()
88
+
89
+
90
+ app: FastAPI = FastAPI(lifespan=lifespan)
91
+ app.add_middleware(BaseHTTPMiddleware, dispatch=shutdown.dispatch)
92
+ ```
93
+
94
+ Set the orchestrator's grace period a little **above** `drain_timeout`,
95
+ and uvicorn's `--timeout-graceful-shutdown` to match.
96
+
97
+ !!! warning "The signal belongs to your server"
98
+ uvicorn already installs `SIGTERM` handlers and triggers the lifespan
99
+ shutdown — drive draining from there. The opt-in
100
+ `install_signal_handlers()` is only for servers that do **not** manage
101
+ signals themselves; it chains the previous handler and is a no-op off
102
+ the main thread.
103
+
104
+ ## Recap
105
+
106
+ - `AlembicHelper.safe_upgrade()` refuses destructive migrations
107
+ (`DestructiveMigrationError`); `force=True` allows them;
108
+ `pending_destructive_ops()` only inspects.
109
+ - `GracefulShutdownMiddleware` replies `503` while draining and
110
+ `wait_drained()` waits for in-flight requests — driven from the
111
+ `lifespan`.
@@ -0,0 +1,110 @@
1
+ # Deploy seguro (migrações + graceful shutdown)
2
+
3
+ Dois riscos clássicos de deploy: uma migration que **apaga dados** sem
4
+ querer, e um rollout que **corta requests no meio** quando o pod velho
5
+ morre. Esta receita cobre as duas defesas que o SDK traz.
6
+
7
+ ## Migrações seguras: `safe_upgrade`
8
+
9
+ `AlembicHelper.safe_upgrade()` roda o upgrade **só se** nenhuma migration
10
+ pendente for destrutiva. Ele varre o `def upgrade()` de cada revision
11
+ pendente atrás de chamadas que apagam dados — `op.drop_table`,
12
+ `op.drop_column`, `op.drop_constraint` (e variantes `batch_op`) — e, se
13
+ achar alguma, levanta `DestructiveMigrationError` **sem tocar no banco**.
14
+
15
+ ```python
16
+ from tempest_fastapi_sdk import AlembicHelper, DestructiveMigrationError
17
+
18
+
19
+ def deploy_migrations() -> None:
20
+ """Aplica migrations no deploy, barrando DROPs acidentais."""
21
+ helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
22
+ try:
23
+ helper.safe_upgrade("head")
24
+ except DestructiveMigrationError as exc:
25
+ # CI/CD falha aqui — alguém precisa revisar e liberar com force.
26
+ for revision, op in exc.offences:
27
+ print(f"bloqueado: {revision} → {op}")
28
+ raise
29
+ ```
30
+
31
+ A varredura olha o **código** da migration, não o SQL gerado — então não
32
+ dá falso-positivo no rebuild de tabela que o SQLite faz em batch mode. Um
33
+ `drop_*` no `downgrade()` (o caminho normal e esperado) é ignorado.
34
+
35
+ ### Liberando um DROP intencional
36
+
37
+ Quando o DROP é proposital (você já fez backup, já validou), passe
38
+ `force=True` — as operações destrutivas são logadas e o upgrade roda:
39
+
40
+ ```python
41
+ from tempest_fastapi_sdk import AlembicHelper
42
+
43
+ helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
44
+ helper.safe_upgrade("head", force=True) # eu sei o que estou fazendo
45
+ ```
46
+
47
+ !!! tip "Só inspecionar"
48
+ `helper.pending_destructive_ops("head")` devolve a lista de
49
+ `(revision, operação)` sem rodar nada — útil pra um passo de CI que só
50
+ reporta.
51
+
52
+ !!! danger "force=True apaga dados"
53
+ `DROP COLUMN` / `DROP TABLE` são irreversíveis. Só use `force=True`
54
+ depois de backup e revisão humana.
55
+
56
+ ## Graceful shutdown: drenar requests em voo
57
+
58
+ No rollout, o orquestrador manda `SIGTERM` e, depois de um tempo,
59
+ `SIGKILL`. Se uma request ainda estiver rodando quando o worker morre, ela
60
+ é cortada — vira um 502 intermitente. `GracefulShutdownMiddleware`:
61
+
62
+ 1. Ao entrar em **drenagem**, responde `503` + `Retry-After` pra requests
63
+ novas, então o load balancer para de rotear pra esse pod.
64
+ 2. **Conta** as requests em voo; `wait_drained()` espera elas terminarem
65
+ (com timeout) antes do processo sair.
66
+
67
+ Você segura a instância e dirige a drenagem pelo `lifespan` (o uvicorn
68
+ roda o shutdown do lifespan no `SIGTERM` — e é ele quem cuida do sinal):
69
+
70
+ ```python
71
+ from contextlib import asynccontextmanager
72
+ from collections.abc import AsyncIterator
73
+
74
+ from fastapi import FastAPI
75
+ from starlette.middleware.base import BaseHTTPMiddleware
76
+
77
+ from tempest_fastapi_sdk import GracefulShutdownMiddleware
78
+
79
+ shutdown: GracefulShutdownMiddleware = GracefulShutdownMiddleware(drain_timeout=25.0)
80
+
81
+
82
+ @asynccontextmanager
83
+ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
84
+ """Drena as requests em voo no shutdown."""
85
+ yield
86
+ shutdown.begin_drain()
87
+ await shutdown.wait_drained()
88
+
89
+
90
+ app: FastAPI = FastAPI(lifespan=lifespan)
91
+ app.add_middleware(BaseHTTPMiddleware, dispatch=shutdown.dispatch)
92
+ ```
93
+
94
+ Configure o grace period do orquestrador um pouco **acima** do
95
+ `drain_timeout`, e o `--timeout-graceful-shutdown` do uvicorn pra casar.
96
+
97
+ !!! warning "O sinal é do seu servidor"
98
+ O uvicorn já instala handlers de `SIGTERM` e dispara o shutdown do
99
+ lifespan — dirija a drenagem por lá. O método opt-in
100
+ `install_signal_handlers()` só serve pra servidores que **não**
101
+ gerenciam sinais sozinhos; ele encadeia o handler anterior e é no-op
102
+ fora da thread principal.
103
+
104
+ ## Recap
105
+
106
+ - `AlembicHelper.safe_upgrade()` recusa migrations destrutivas
107
+ (`DestructiveMigrationError`); `force=True` libera; `pending_destructive_ops()`
108
+ só inspeciona.
109
+ - `GracefulShutdownMiddleware` responde `503` durante a drenagem e
110
+ `wait_drained()` espera as requests em voo — dirigido pelo `lifespan`.
@@ -10,6 +10,7 @@ Bite-sized "I want to wire X" walkthroughs. Each page starts with **what problem
10
10
  | Theme | Covers |
11
11
  | --- | --- |
12
12
  | **[Database »](database.md)** | `BaseRepository`, `BaseModel`, `AuditMixin` / `SoftDeleteMixin`, cursor pagination, Alembic helper |
13
+ | **[Multi-tenant »](multi-tenant.md)** | `TenantScopedRepository` — `tenant_id` isolation on every query |
13
14
  | **[HTTP layer »](http.md)** | `apply_cors`, `RequestIDMiddleware`, `RateLimitMiddleware`, `make_health_router`, JWT / role / permission dependencies, webhook signature verifier, pagination Link headers, tool-spec router |
14
15
  | **[HTTP client (outbound) »](http-client.md)** | `HTTPClient` — typed httpx with retry/backoff, circuit-breaker, X-Request-ID; `RetryPolicy`, `CircuitOpenError` |
15
16
  | **[Cache »](cache.md)** | `AsyncRedisManager`, `@cached` decorator |
@@ -25,6 +26,7 @@ Bite-sized "I want to wire X" walkthroughs. Each page starts with **what problem
25
26
  | **[Downloads »](downloads.md)** | `DownloadUtils` — `file_response`, `stream`, `build_content_disposition`, path-traversal safe |
26
27
  | **[Utilities »](utilities.md)** | `utcnow`/`to_utc`, `modify_dict`, `get_client_ip`, opaque tokens (`generate_opaque_token`) |
27
28
  | **[Testing »](testing.md)** | `test_session`, `test_database`, in-memory SQLite, pytest fixtures |
29
+ | **[Safe deploys »](deploy-safety.md)** | `AlembicHelper.safe_upgrade` (blocks DROPs), `GracefulShutdownMiddleware` |
28
30
  | **[CLI »](cli.md)** | `tempest new` / `lint` / `fix` / `format` / `type` / `test` / `check` |
29
31
  | **[Security »](security.md)** | `AttemptThrottle`, opaque-token helpers, `HardenedStaticFiles`, security headers |
30
32
  | **[Brazilian helpers »](br-helpers.md)** | CPF / CNPJ / CEP / phone validation + normalization |
@@ -10,6 +10,7 @@ Passo a passo curtos no estilo "quero conectar X". Cada página começa com **qu
10
10
  | Tema | Cobre |
11
11
  | --- | --- |
12
12
  | **[Banco de dados »](database.md)** | `BaseRepository`, `BaseModel`, `AuditMixin` / `SoftDeleteMixin`, paginação por cursor, helper de Alembic |
13
+ | **[Multi-tenant »](multi-tenant.md)** | `TenantScopedRepository` — isolamento por `tenant_id` em toda query |
13
14
  | **[Camada HTTP »](http.md)** | `apply_cors`, `RequestIDMiddleware`, `RateLimitMiddleware`, `make_health_router`, dependências de JWT / role / permissão, verificador de assinatura de webhook, headers Link de paginação, router de tool-spec |
14
15
  | **[HTTP client (saída) »](http-client.md)** | `HTTPClient` — httpx tipado com retry/backoff, circuit-breaker, X-Request-ID; `RetryPolicy`, `CircuitOpenError` |
15
16
  | **[Cache »](cache.md)** | `AsyncRedisManager`, decorator `@cached` |
@@ -25,6 +26,7 @@ Passo a passo curtos no estilo "quero conectar X". Cada página começa com **qu
25
26
  | **[Downloads »](downloads.md)** | `DownloadUtils` — `file_response`, `stream`, `build_content_disposition`, anti path-traversal |
26
27
  | **[Utilitários »](utilities.md)** | `utcnow`/`to_utc`, `modify_dict`, `get_client_ip`, tokens opacos (`generate_opaque_token`) |
27
28
  | **[Testes »](testing.md)** | `test_session`, `test_database`, SQLite em memória, fixtures pytest |
29
+ | **[Deploy seguro »](deploy-safety.md)** | `AlembicHelper.safe_upgrade` (barra DROPs), `GracefulShutdownMiddleware` |
28
30
  | **[CLI »](cli.md)** | `tempest new` / `lint` / `fix` / `format` / `type` / `test` / `check` |
29
31
  | **[Segurança »](security.md)** | `AttemptThrottle`, helpers de token opaco, `HardenedStaticFiles`, headers de segurança |
30
32
  | **[Helpers brasileiros »](br-helpers.md)** | validação + normalização de CPF / CNPJ / CEP / telefone |
@@ -0,0 +1,120 @@
1
+ # Multi-tenant (TenantScopedRepository)
2
+
3
+ In a **shared-schema** multi-tenant database, every tenant's rows live in
4
+ the same table, told apart by a `tenant_id` column. The danger is obvious:
5
+ forget **one** `WHERE tenant_id = ?` and tenant A reads (or deletes)
6
+ tenant B's data. `TenantScopedRepository` takes that risk off the table —
7
+ you bind the `tenant_id` at construction and it injects the filter into
8
+ **every** read and stamps it onto **every** write. Call sites can't get it
9
+ wrong.
10
+
11
+ !!! info "Where this fits"
12
+ It's a `BaseRepository` that behaves identically — same API
13
+ ([Database](database.md)). The difference is invisible to the caller:
14
+ tenant scoping is automatic.
15
+
16
+ ## 1. The model needs a tenant column
17
+
18
+ ```python
19
+ from uuid import UUID
20
+
21
+ from sqlalchemy import String, Uuid
22
+ from sqlalchemy.orm import Mapped, mapped_column
23
+
24
+ from tempest_fastapi_sdk import BaseModel
25
+
26
+
27
+ class OrderModel(BaseModel):
28
+ """Order — isolated per tenant."""
29
+
30
+ __tablename__ = "order"
31
+
32
+ tenant_id: Mapped[UUID] = mapped_column(Uuid(), nullable=False, index=True)
33
+ total: Mapped[int] = mapped_column(nullable=False)
34
+ ```
35
+
36
+ ## 2. Build the repository bound to the tenant
37
+
38
+ The `tenant_id` usually comes from the request's JWT / session / header:
39
+
40
+ ```python
41
+ from uuid import UUID
42
+
43
+ from sqlalchemy.ext.asyncio import AsyncSession
44
+
45
+ from tempest_fastapi_sdk import TenantScopedRepository
46
+
47
+ from src.db.models import OrderModel
48
+
49
+
50
+ def get_order_repo(
51
+ session: AsyncSession, tenant_id: UUID
52
+ ) -> TenantScopedRepository[OrderModel]:
53
+ """Order repository locked to the request's tenant."""
54
+ return TenantScopedRepository(session, model=OrderModel, tenant_id=tenant_id)
55
+ ```
56
+
57
+ If the model has no `tenant_id` column, the constructor raises
58
+ `AttributeError` immediately — you catch the mistake at boot, not in
59
+ production. Different column name? Pass `tenant_field="org_id"`.
60
+
61
+ ## 3. Use it like any repository
62
+
63
+ Every read comes filtered; every write comes stamped:
64
+
65
+ ```python
66
+ from uuid import UUID
67
+
68
+ from tempest_fastapi_sdk import TenantScopedRepository
69
+
70
+ from src.db.models import OrderModel
71
+
72
+
73
+ async def list_orders(repo: TenantScopedRepository[OrderModel]) -> list[OrderModel]:
74
+ """Only THIS tenant's orders — no manual WHERE."""
75
+ return await repo.list() # WHERE tenant_id = <bound tenant>
76
+
77
+
78
+ async def create_order(
79
+ repo: TenantScopedRepository[OrderModel], total: int
80
+ ) -> OrderModel:
81
+ """tenant_id is stamped automatically on insert."""
82
+ return await repo.add(OrderModel(total=total))
83
+ ```
84
+
85
+ ### Cross-tenant access is impossible, even by id
86
+
87
+ `get_by_id`, `delete` and `delete_batch` are scoped too. An id from another
88
+ tenant simply **doesn't match** — indistinguishable from a row that never
89
+ existed:
90
+
91
+ ```python
92
+ from uuid import UUID
93
+
94
+ from tempest_fastapi_sdk import TenantScopedRepository
95
+
96
+ from src.db.models import OrderModel
97
+
98
+
99
+ async def fetch(repo: TenantScopedRepository[OrderModel], order_id: UUID) -> OrderModel:
100
+ """Raises NotFound if the order belongs to ANOTHER tenant — no existence leak."""
101
+ return await repo.get_by_id(order_id)
102
+ ```
103
+
104
+ `delete_many({})` deletes only **this** tenant's rows, never the whole
105
+ table — the tenant predicate is always added.
106
+
107
+ !!! warning "Raw queries are your responsibility"
108
+ A subclass that builds its own `select(...)` without going through
109
+ `_apply_filters` — or a pre-built `query=` passed to `paginate` — is
110
+ **not** auto-scoped. In those cases, add
111
+ `.where(self.tenant_column == self.tenant_id)` yourself.
112
+
113
+ ## Recap
114
+
115
+ - The model declares `tenant_id` (or another column, via `tenant_field=`).
116
+ - `TenantScopedRepository(session, model=..., tenant_id=...)` injects the
117
+ filter into every read and stamps every write.
118
+ - `get_by_id` / `delete` / `delete_batch` / `delete_many` are scoped —
119
+ cross-tenant access is impossible through the repository methods.
120
+ - The constructor validates the tenant column exists at boot.
@@ -0,0 +1,120 @@
1
+ # Multi-tenant (TenantScopedRepository)
2
+
3
+ Num banco multi-tenant de **schema compartilhado**, as linhas de todos os
4
+ tenants moram na mesma tabela, separadas por uma coluna `tenant_id`. O
5
+ perigo é claro: esquecer **um** `WHERE tenant_id = ?` e o tenant A lê (ou
6
+ apaga) os dados do tenant B. `TenantScopedRepository` tira esse risco da
7
+ mesa — você amarra o `tenant_id` na construção e ele injeta o filtro em
8
+ **toda** leitura e carimba em **toda** escrita. Os call sites não têm como
9
+ errar.
10
+
11
+ !!! info "Onde isso encaixa"
12
+ É um `BaseRepository` que se comporta igualzinho — mesma API
13
+ ([Banco de dados](database.md)). A diferença é invisível pro chamador:
14
+ o escopo de tenant é automático.
15
+
16
+ ## 1. O modelo precisa da coluna de tenant
17
+
18
+ ```python
19
+ from uuid import UUID
20
+
21
+ from sqlalchemy import String, Uuid
22
+ from sqlalchemy.orm import Mapped, mapped_column
23
+
24
+ from tempest_fastapi_sdk import BaseModel
25
+
26
+
27
+ class OrderModel(BaseModel):
28
+ """Pedido — isolado por tenant."""
29
+
30
+ __tablename__ = "order"
31
+
32
+ tenant_id: Mapped[UUID] = mapped_column(Uuid(), nullable=False, index=True)
33
+ total: Mapped[int] = mapped_column(nullable=False)
34
+ ```
35
+
36
+ ## 2. Construa o repositório amarrado ao tenant
37
+
38
+ O `tenant_id` normalmente vem do JWT / sessão / header do request:
39
+
40
+ ```python
41
+ from uuid import UUID
42
+
43
+ from sqlalchemy.ext.asyncio import AsyncSession
44
+
45
+ from tempest_fastapi_sdk import TenantScopedRepository
46
+
47
+ from src.db.models import OrderModel
48
+
49
+
50
+ def get_order_repo(
51
+ session: AsyncSession, tenant_id: UUID
52
+ ) -> TenantScopedRepository[OrderModel]:
53
+ """Repositório de pedidos travado no tenant do request."""
54
+ return TenantScopedRepository(session, model=OrderModel, tenant_id=tenant_id)
55
+ ```
56
+
57
+ Se o modelo não tiver a coluna `tenant_id`, o construtor levanta
58
+ `AttributeError` na hora — você descobre o erro no boot, não em produção.
59
+ Coluna com outro nome? Passe `tenant_field="org_id"`.
60
+
61
+ ## 3. Use como qualquer repositório
62
+
63
+ Toda leitura já vem filtrada; toda escrita já vem carimbada:
64
+
65
+ ```python
66
+ from uuid import UUID
67
+
68
+ from tempest_fastapi_sdk import TenantScopedRepository
69
+
70
+ from src.db.models import OrderModel
71
+
72
+
73
+ async def list_orders(repo: TenantScopedRepository[OrderModel]) -> list[OrderModel]:
74
+ """Só os pedidos DESTE tenant — sem WHERE manual."""
75
+ return await repo.list() # WHERE tenant_id = <tenant amarrado>
76
+
77
+
78
+ async def create_order(
79
+ repo: TenantScopedRepository[OrderModel], total: int
80
+ ) -> OrderModel:
81
+ """tenant_id é carimbado automaticamente no insert."""
82
+ return await repo.add(OrderModel(total=total))
83
+ ```
84
+
85
+ ### Acesso cruzado é impossível, mesmo por id
86
+
87
+ `get_by_id`, `delete` e `delete_batch` também são escopados. Um id de outro
88
+ tenant simplesmente **não casa** — é indistinguível de uma linha que nunca
89
+ existiu:
90
+
91
+ ```python
92
+ from uuid import UUID
93
+
94
+ from tempest_fastapi_sdk import TenantScopedRepository
95
+
96
+ from src.db.models import OrderModel
97
+
98
+
99
+ async def fetch(repo: TenantScopedRepository[OrderModel], order_id: UUID) -> OrderModel:
100
+ """Levanta NotFound se o pedido for de OUTRO tenant — sem vazar existência."""
101
+ return await repo.get_by_id(order_id)
102
+ ```
103
+
104
+ `delete_many({})` apaga só as linhas **deste** tenant, nunca a tabela
105
+ inteira — o predicado de tenant é sempre adicionado.
106
+
107
+ !!! warning "Queries cruas são responsabilidade sua"
108
+ Uma subclasse que monta o próprio `select(...)` sem passar por
109
+ `_apply_filters` — ou um `query=` pré-montado passado pro `paginate` —
110
+ **não** é escopado automaticamente. Nesses casos, adicione
111
+ `.where(self.tenant_column == self.tenant_id)` você mesmo.
112
+
113
+ ## Recap
114
+
115
+ - O modelo declara `tenant_id` (ou outra coluna, via `tenant_field=`).
116
+ - `TenantScopedRepository(session, model=..., tenant_id=...)` injeta o
117
+ filtro em toda leitura e carimba toda escrita.
118
+ - `get_by_id` / `delete` / `delete_batch` / `delete_many` são escopados —
119
+ acesso cruzado entre tenants é impossível pelos métodos do repositório.
120
+ - Construtor valida a existência da coluna de tenant no boot.
@@ -31,6 +31,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
31
31
  ::: tempest_fastapi_sdk.db.user_recovery_code_model.BaseUserRecoveryCodeModel
32
32
  ::: tempest_fastapi_sdk.db.user_recovery_code_model.make_user_recovery_code_model
33
33
  ::: tempest_fastapi_sdk.db.repository.BaseRepository
34
+ ::: tempest_fastapi_sdk.db.tenant.TenantScopedRepository
34
35
  ::: tempest_fastapi_sdk.db.mixins.SoftDeleteMixin
35
36
  ::: tempest_fastapi_sdk.db.mixins.AuditMixin
36
37
  ::: tempest_fastapi_sdk.db.mixins.MFAMixin
@@ -40,6 +41,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
40
41
  ::: tempest_fastapi_sdk.db.outbox.BaseOutboxModel
41
42
  ::: tempest_fastapi_sdk.db.outbox.OutboxRelay
42
43
  ::: tempest_fastapi_sdk.db.outbox.OutboxStatus
44
+ ::: tempest_fastapi_sdk.db.migrations.DestructiveMigrationError
43
45
 
44
46
  ---
45
47
 
@@ -95,6 +97,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
95
97
  ::: tempest_fastapi_sdk.api.middlewares.idempotency.MemoryIdempotencyStore
96
98
  ::: tempest_fastapi_sdk.api.middlewares.idempotency.RedisIdempotencyStore
97
99
  ::: tempest_fastapi_sdk.api.middlewares.body_size.BodySizeLimitMiddleware
100
+ ::: tempest_fastapi_sdk.api.middlewares.graceful.GracefulShutdownMiddleware
98
101
  ::: tempest_fastapi_sdk.api.middlewares.csrf.CSRFMiddleware
99
102
  ::: tempest_fastapi_sdk.api.middlewares.csrf.make_csrf_token_dependency
100
103
  ::: tempest_fastapi_sdk.api.middlewares.csrf.generate_csrf_token
@@ -86,6 +86,7 @@ plugins:
86
86
  Tutorial: Tutorial
87
87
  Receitas: Recipes
88
88
  Banco de dados: Database
89
+ Multi-tenant: Multi-tenant
89
90
  Camada HTTP: HTTP layer
90
91
  "HTTP client (saída)": "HTTP client (outbound)"
91
92
  Cache: Cache
@@ -106,6 +107,7 @@ plugins:
106
107
  Métricas: Metrics
107
108
  Painel admin: Admin site
108
109
  Testes: Testing
110
+ "Deploy seguro (migrations + shutdown)": "Safe deploys (migrations + shutdown)"
109
111
  CLI: CLI
110
112
  Segurança: Security
111
113
  Utilitários: Utilities
@@ -200,6 +202,7 @@ nav:
200
202
  - Receitas:
201
203
  - recipes/index.md
202
204
  - Banco de dados: recipes/database.md
205
+ - Multi-tenant: recipes/multi-tenant.md
203
206
  - Camada HTTP: recipes/http.md
204
207
  - "HTTP client (saída)": recipes/http-client.md
205
208
  - Cache: recipes/cache.md
@@ -221,6 +224,7 @@ nav:
221
224
  - Métricas: recipes/metrics.md
222
225
  - Painel admin: recipes/admin.md
223
226
  - Testes: recipes/testing.md
227
+ - "Deploy seguro (migrations + shutdown)": recipes/deploy-safety.md
224
228
  - CLI: recipes/cli.md
225
229
  - Segurança: recipes/security.md
226
230
  - Utilitários: recipes/utilities.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tempest-fastapi-sdk"
3
- version = "0.44.0"
3
+ version = "0.46.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"