tempest-fastapi-sdk 0.53.0__tar.gz → 0.55.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 (402) hide show
  1. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/CHANGELOG.md +56 -0
  2. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/CLAUDE.md +52 -53
  3. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/PKG-INFO +33 -15
  4. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/README.md +32 -14
  5. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http.en.md +115 -13
  6. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http.md +115 -13
  7. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/reference.md +12 -0
  8. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/pyproject.toml +1 -1
  9. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/__init__.py +25 -1
  10. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/__init__.py +16 -0
  11. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/handlers.py +43 -2
  12. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +19 -1
  13. tempest_fastapi_sdk-0.55.0/tempest_fastapi_sdk/api/middlewares/rate_limit.py +553 -0
  14. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/__init__.py +10 -0
  15. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/base.py +26 -1
  16. tempest_fastapi_sdk-0.55.0/tempest_fastapi_sdk/exceptions/i18n.py +231 -0
  17. tempest_fastapi_sdk-0.55.0/tests/api/test_rate_limit_extras.py +287 -0
  18. tempest_fastapi_sdk-0.55.0/tests/exceptions/test_i18n.py +209 -0
  19. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/uv.lock +1 -1
  20. tempest_fastapi_sdk-0.53.0/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -142
  21. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.github/workflows/ci.yml +0 -0
  22. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.github/workflows/docs.yml +0 -0
  23. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.github/workflows/release-pypi.yml +0 -0
  24. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.gitignore +0 -0
  25. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.python-version +0 -0
  26. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/Makefile +0 -0
  27. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/architecture.en.md +0 -0
  28. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/architecture.md +0 -0
  29. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/changelog.en.md +0 -0
  30. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/changelog.md +0 -0
  31. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/contributing.en.md +0 -0
  32. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/contributing.md +0 -0
  33. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/index.en.md +0 -0
  34. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/index.md +0 -0
  35. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/installation.en.md +0 -0
  36. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/installation.md +0 -0
  37. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/index.en.md +0 -0
  38. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/index.md +0 -0
  39. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/api.en.md +0 -0
  40. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/api.md +0 -0
  41. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/business-rules.en.md +0 -0
  42. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/business-rules.md +0 -0
  43. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/domain.en.md +0 -0
  44. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/domain.md +0 -0
  45. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/flows.en.md +0 -0
  46. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/flows.md +0 -0
  47. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/index.en.md +0 -0
  48. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/index.md +0 -0
  49. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/migration.en.md +0 -0
  50. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/migration.md +0 -0
  51. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/admin.en.md +0 -0
  52. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/admin.md +0 -0
  53. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/auth-flow.en.md +0 -0
  54. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/auth-flow.md +0 -0
  55. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/br-helpers.en.md +0 -0
  56. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/br-helpers.md +0 -0
  57. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cache.en.md +0 -0
  58. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cache.md +0 -0
  59. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cli.en.md +0 -0
  60. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cli.md +0 -0
  61. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/database.en.md +0 -0
  62. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/database.md +0 -0
  63. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/deploy-safety.en.md +0 -0
  64. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/deploy-safety.md +0 -0
  65. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/downloads.en.md +0 -0
  66. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/downloads.md +0 -0
  67. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/email.en.md +0 -0
  68. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/email.md +0 -0
  69. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http-client.en.md +0 -0
  70. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http-client.md +0 -0
  71. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/idempotency.en.md +0 -0
  72. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/idempotency.md +0 -0
  73. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/index.en.md +0 -0
  74. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/index.md +0 -0
  75. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/logging.en.md +0 -0
  76. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/logging.md +0 -0
  77. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/metrics.en.md +0 -0
  78. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/metrics.md +0 -0
  79. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/mfa.en.md +0 -0
  80. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/mfa.md +0 -0
  81. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/multi-tenant.en.md +0 -0
  82. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/multi-tenant.md +0 -0
  83. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/observability.en.md +0 -0
  84. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/observability.md +0 -0
  85. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/offline-sync.en.md +0 -0
  86. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/offline-sync.md +0 -0
  87. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/outbox.en.md +0 -0
  88. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/outbox.md +0 -0
  89. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/queue-tasks.en.md +0 -0
  90. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/queue-tasks.md +0 -0
  91. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/realtime.en.md +0 -0
  92. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/realtime.md +0 -0
  93. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/security.en.md +0 -0
  94. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/security.md +0 -0
  95. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/sessions.en.md +0 -0
  96. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/sessions.md +0 -0
  97. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/storage.en.md +0 -0
  98. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/storage.md +0 -0
  99. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/testing.en.md +0 -0
  100. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/testing.md +0 -0
  101. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/uploads.en.md +0 -0
  102. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/uploads.md +0 -0
  103. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/utilities.en.md +0 -0
  104. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/utilities.md +0 -0
  105. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/webpush.en.md +0 -0
  106. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/webpush.md +0 -0
  107. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/websocket.en.md +0 -0
  108. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/websocket.md +0 -0
  109. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/reference.en.md +0 -0
  110. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/roadmap.en.md +0 -0
  111. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/roadmap.md +0 -0
  112. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/tutorial.en.md +0 -0
  113. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/tutorial.md +0 -0
  114. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/mkdocs.yml +0 -0
  115. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/scripts/extract_recipe.py +0 -0
  116. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
  117. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
  118. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/config.py +0 -0
  119. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
  120. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/router.py +0 -0
  121. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/session.py +0 -0
  122. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/site.py +0 -0
  123. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
  124. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
  125. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
  126. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
  127. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
  128. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
  129. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
  130. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
  131. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
  132. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
  133. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
  134. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
  135. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
  136. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
  137. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
  138. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
  139. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
  140. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
  141. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
  142. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
  143. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
  144. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
  145. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
  146. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
  147. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/server.py +0 -0
  148. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/static.py +0 -0
  149. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
  150. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
  151. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
  152. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/guards.py +0 -0
  153. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
  154. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/router.py +0 -0
  155. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
  156. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/service.py +0 -0
  157. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/activation.html +0 -0
  158. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/activation_error.html +0 -0
  159. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/activation_success.html +0 -0
  160. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset.html +0 -0
  161. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset_error.html +0 -0
  162. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset_form.html +0 -0
  163. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset_success.html +0 -0
  164. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
  165. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
  166. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
  167. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
  168. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
  169. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
  170. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
  171. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
  172. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
  173. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
  174. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
  175. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
  176. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
  177. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
  178. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
  179. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
  180. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
  181. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
  182. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
  183. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
  184. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
  185. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
  186. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
  187. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
  188. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
  189. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
  190. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
  191. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
  192. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
  193. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
  194. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/db.py +0 -0
  195. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
  196. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
  197. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
  198. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/main.py +0 -0
  199. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/new.py +0 -0
  200. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
  201. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
  202. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/user.py +0 -0
  203. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
  204. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
  205. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
  206. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/context.py +0 -0
  207. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/enums.py +0 -0
  208. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/logging.py +0 -0
  209. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
  210. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
  211. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
  212. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
  213. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/connection.py +0 -0
  214. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
  215. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
  216. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/model.py +0 -0
  217. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
  218. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/repository.py +0 -0
  219. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
  220. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
  221. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
  222. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
  223. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
  224. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
  225. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
  226. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
  227. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
  228. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
  229. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
  230. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
  231. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
  232. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/py.typed +0 -0
  233. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
  234. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
  235. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
  236. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
  237. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
  238. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
  239. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
  240. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
  241. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
  242. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/services/base.py +0 -0
  243. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
  244. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
  245. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
  246. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
  247. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
  248. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
  249. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
  250. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
  251. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/settings/base.py +0 -0
  252. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
  253. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
  254. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
  255. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
  256. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
  257. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
  258. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
  259. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
  260. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
  261. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/testing/database.py +0 -0
  262. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
  263. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
  264. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/data/br_locations.json +0 -0
  265. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
  266. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
  267. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/download.py +0 -0
  268. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/email.py +0 -0
  269. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
  270. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
  271. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/locations.py +0 -0
  272. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/log.py +0 -0
  273. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
  274. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
  275. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/password.py +0 -0
  276. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
  277. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
  278. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
  279. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
  280. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
  281. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
  282. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
  283. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
  284. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
  285. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
  286. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
  287. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
  288. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/__init__.py +0 -0
  289. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/__init__.py +0 -0
  290. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_auth.py +0 -0
  291. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_forms.py +0 -0
  292. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_logs_nav.py +0 -0
  293. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_mfa.py +0 -0
  294. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_router.py +0 -0
  295. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_site.py +0 -0
  296. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_user_model.py +0 -0
  297. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/__init__.py +0 -0
  298. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_body_size.py +0 -0
  299. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_cookies.py +0 -0
  300. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_cors.py +0 -0
  301. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_csrf.py +0 -0
  302. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_dependencies_auth.py +0 -0
  303. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_graceful.py +0 -0
  304. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_handlers.py +0 -0
  305. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_health_router.py +0 -0
  306. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_idempotency.py +0 -0
  307. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_jwt_dependency.py +0 -0
  308. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_logs_router.py +0 -0
  309. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_oauth.py +0 -0
  310. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_prometheus.py +0 -0
  311. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_rate_limit.py +0 -0
  312. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_request_id_middleware.py +0 -0
  313. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_role_dependency.py +0 -0
  314. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_server.py +0 -0
  315. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_static.py +0 -0
  316. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_tool_spec.py +0 -0
  317. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_tracing.py +0 -0
  318. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_webhooks.py +0 -0
  319. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_webhooks_rsa.py +0 -0
  320. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/__init__.py +0 -0
  321. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/test_guards.py +0 -0
  322. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/test_mfa.py +0 -0
  323. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/test_service.py +0 -0
  324. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cache/__init__.py +0 -0
  325. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cache/test_decorator.py +0 -0
  326. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cache/test_redis_manager.py +0 -0
  327. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/__init__.py +0 -0
  328. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_db.py +0 -0
  329. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_db_seed.py +0 -0
  330. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_docker_compose.py +0 -0
  331. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_generate.py +0 -0
  332. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_main.py +0 -0
  333. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_secrets.py +0 -0
  334. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_user.py +0 -0
  335. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/conftest.py +0 -0
  336. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/controllers/__init__.py +0 -0
  337. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/controllers/test_base.py +0 -0
  338. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/__init__.py +0 -0
  339. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/test_context.py +0 -0
  340. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/test_enums.py +0 -0
  341. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/test_logging.py +0 -0
  342. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/__init__.py +0 -0
  343. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_alembic_hooks.py +0 -0
  344. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_bulk_ops.py +0 -0
  345. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_connection.py +0 -0
  346. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_migrations.py +0 -0
  347. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_mixins.py +0 -0
  348. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_model.py +0 -0
  349. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_outbox.py +0 -0
  350. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_repository.py +0 -0
  351. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_safe_upgrade.py +0 -0
  352. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_slow_query.py +0 -0
  353. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_tenant.py +0 -0
  354. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/exceptions/__init__.py +0 -0
  355. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/exceptions/test_exceptions.py +0 -0
  356. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/queue/__init__.py +0 -0
  357. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/queue/test_manager.py +0 -0
  358. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/__init__.py +0 -0
  359. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_base.py +0 -0
  360. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_cursor_pagination.py +0 -0
  361. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_link_headers.py +0 -0
  362. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_pagination.py +0 -0
  363. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_response.py +0 -0
  364. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/services/__init__.py +0 -0
  365. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/services/test_base.py +0 -0
  366. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sessions/__init__.py +0 -0
  367. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sessions/test_sessions.py +0 -0
  368. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/settings/__init__.py +0 -0
  369. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/settings/test_base.py +0 -0
  370. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/settings/test_mixins.py +0 -0
  371. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sse/__init__.py +0 -0
  372. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sse/test_event_stream.py +0 -0
  373. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/storage/__init__.py +0 -0
  374. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/storage/test_minio_client.py +0 -0
  375. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/tasks/__init__.py +0 -0
  376. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/tasks/test_manager.py +0 -0
  377. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/tasks/test_scheduler.py +0 -0
  378. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/testing/__init__.py +0 -0
  379. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/testing/test_database.py +0 -0
  380. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/__init__.py +0 -0
  381. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_client_ip.py +0 -0
  382. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_datetime.py +0 -0
  383. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_dict.py +0 -0
  384. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_download.py +0 -0
  385. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_email.py +0 -0
  386. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_http_client.py +0 -0
  387. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_jwt.py +0 -0
  388. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_lazy_extras.py +0 -0
  389. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_locations.py +0 -0
  390. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_log.py +0 -0
  391. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_metrics.py +0 -0
  392. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_opaque_token.py +0 -0
  393. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_password.py +0 -0
  394. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_regex.py +0 -0
  395. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_storage_backends.py +0 -0
  396. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_throttle.py +0 -0
  397. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_upload.py +0 -0
  398. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/webpush/__init__.py +0 -0
  399. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/webpush/test_dispatcher.py +0 -0
  400. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/webpush/test_schemas.py +0 -0
  401. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/websockets/__init__.py +0 -0
  402. {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/websockets/test_hub_and_router.py +0 -0
@@ -5,6 +5,62 @@ 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.55.0] — 2026-06-14
9
+
10
+ ### Added
11
+
12
+ - **Localized error envelopes (i18n) for `AppException`** — the error
13
+ `detail` can now be resolved per request locale instead of being
14
+ English-only, without callers hand-translating each `raise`.
15
+ - **`MessageCatalog`** — maps `(locale, key) -> template`, with
16
+ case-insensitive locale matching and primary-subtag fallback
17
+ (a catalog holding `pt-BR` answers `pt`, and vice versa).
18
+ `resolve()` interpolates `message_params` via `str.format`
19
+ (a missing param returns the template instead of raising);
20
+ `negotiate()` picks the best locale from an `Accept-Language`
21
+ header; `merge()` overlays domain codes / new locales onto a
22
+ base catalog without mutating it.
23
+ - **`default_message_catalog()`** — PT-BR (default) + EN-US strings
24
+ for every built-in exception code (`NOT_FOUND`, `CONFLICT`,
25
+ `UNAUTHORIZED`, `FORBIDDEN`, `VALIDATION_ERROR`,
26
+ `TOO_MANY_REQUESTS`, `INVALID_TOKEN`, `TOKEN_EXPIRED`,
27
+ `FILE_TOO_LARGE`, `INVALID_FILE_TYPE`, `INTERNAL_SERVER_ERROR`).
28
+ - **`parse_accept_language()`** + **`DEFAULT_LOCALE`** (`"pt-BR"`).
29
+ - **`AppException` gains `message_key` / `message_params`** — the
30
+ catalog key (defaults to the exception `code`) and template
31
+ values. **`register_exception_handlers(app, catalog=...,
32
+ default_locale=...)`** and `make_app_exception_handler` accept the
33
+ catalog and localize `detail` from the negotiated locale.
34
+ - Fully backward compatible: with no `catalog` the literal
35
+ `message` is used exactly as before; a missing translation falls
36
+ back to the exception's own `detail`. All new symbols are
37
+ exported at the package top level.
38
+
39
+ ## [0.54.0] — 2026-06-14
40
+
41
+ ### Added
42
+
43
+ - **Per-principal & distributed rate limiting** — `RateLimitMiddleware`
44
+ gains a pluggable store and ready-made key extractors, so limits can
45
+ be per user / tenant / API key and shared across replicas.
46
+ - **Pluggable store** — `RateLimitStore` protocol with
47
+ `MemoryRateLimitStore` (default, in-process) and
48
+ `RedisRateLimitStore` (distributed). The Redis store uses an
49
+ atomic Lua sliding-window log over a sorted set — no race between
50
+ count and add — and `fail_open=True` (default) allows the request
51
+ on a transient Redis error. `RateLimitResult` carries the
52
+ `allowed` / `remaining` / `retry_after` decision.
53
+ - **Key extractors** — `key_by_ip`, `key_by_jwt_subject`
54
+ (per-user via the `sub` claim), `key_by_jwt_claim` (per arbitrary
55
+ claim, e.g. `tenant_id`) and `key_by_header` (e.g. an API key).
56
+ Because the middleware runs before FastAPI dependencies, the
57
+ `key_by_jwt_*` factories decode the bearer from the raw request
58
+ (`decode_or_none`) and fall back to the client IP for anonymous
59
+ traffic.
60
+ - Fully backward compatible: the default behavior (in-process,
61
+ per-IP) is unchanged; `store=` and the `key_by_*` factories are
62
+ opt-in. All new symbols are exported at the package top level.
63
+
8
64
  ## [0.53.0] — 2026-06-13
9
65
 
10
66
  ### Added
@@ -99,63 +99,62 @@ The SDK currently covers (Sep 2025+, post-v0.31.x):
99
99
  - **Cache** — Redis manager + `@cached`.
100
100
  - **Queue / tasks** — FastStream + TaskIQ wrappers.
101
101
  - **BR validators** — CPF/CNPJ/CEP/phone.
102
+ - **BR localities** — `UF` (StrEnum, 27 siglas) + `Region`
103
+ (5 macro-regiões IBGE), `StateBR`/`CityBR` schemas, offline
104
+ dataset of 27 states + 5606 municipalities (IBGE-derived,
105
+ DF as 36 administrative regions), `list_states`/`get_state`/
106
+ `cities_by_uf`/`states_by_region`, `is_valid_uf`/`normalize_uf`,
107
+ `is_valid_city`/`normalize_city` (accent/case-insensitive),
108
+ `UFField`/`CityNameField`.
102
109
  - **Admin panel** — Jinja + HTMX (`AdminSite`, `AdminModel`,
103
110
  `make_admin_router`).
104
111
  - **CLI** — `tempest new` (scaffolds layered service +
105
112
  docker-compose), `tempest generate --docker` (regen compose),
106
- `tempest db init/revision/upgrade/downgrade/current/history`,
107
- `tempest user create [--admin] / list`, plus quality gates
108
- (`lint`, `fix`, `format`, `fmt-check`, `type`, `test`,
109
- `check`).
110
-
111
- The list below is the deliberate next-version plan. Each tier is
112
- ordered by impact for a typical production FastAPI service.
113
-
114
- ### Tier S every serious API needs these
115
-
116
- | Feature | Why it matters |
117
- |---------|----------------|
118
- | **`IdempotencyMiddleware`** + `idempotency_keys` table | Required header for POST on payment/webhook/retry paths. Without it, retried requests duplicate rows. Stripe/AWS pattern. |
119
- | **`UploadUtils` pluggable backends** (`LocalBackend`, `S3Backend(bucket, region)`, `GCSBackend`) | Today only writes to local disk — unusable in any multi-replica deploy. |
120
- | **OpenTelemetry tracing** — `setup_tracing(app, otlp_endpoint=…)` | `RequestIDMiddleware` correlates logs but doesn't give cross-service spans. Needs auto-instrumentation for FastAPI/SQLAlchemy/httpx. |
121
- | **`HTTPClient` (typed httpx wrapper)** | Retry + backoff, `X-Request-ID` propagation, circuit breaker, default timeouts. Today every service rolls raw httpx. |
122
- | **Outbox pattern** — `BaseRepository.save_with_outbox(model, event)` | Persists event in the same tx as the INSERT; `AsyncBrokerManager` drains it. Without this, events are lost when the broker fails after commit. |
123
-
124
- ### Tier A common in SaaS backends
125
-
126
- | Feature | Why it matters |
127
- |---------|----------------|
128
- | **`EmailUtils.render_template(path, ctx)`** with Jinja2 | Welcome / reset / verify emails — today SMTP only accepts raw strings. |
129
- | **OAuth2 / OIDC providers** `GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider(discovery_url)` | `JWTUtils` only signs our own tokens; we have no social-login glue. |
130
- | **`CSRFMiddleware` + `BodySizeLimitMiddleware`** | Admin module currently has no CSRF token; no body limit means DoS via giant upload before `UploadUtils.max_size_bytes` is checked. |
131
- | **`BaseRepository.bulk_create / bulk_update / bulk_upsert`** | Row-by-row inserts are the #1 N+1 bottleneck. SQLAlchemy 2.0 has `insert().values([...])` + `on_conflict_do_update`. |
132
- | **Prometheus `/metrics` endpoint** | `MetricsUtils` already collects the data needs the Prometheus exposition format for oncall scrape. |
133
- | **Admin CSRF token + `make_csrf_token_dependency`** | Admin accepts POST without a token today. |
134
-
135
- ### Tier B when the service grows
136
-
137
- - **2FA / TOTP** (`pyotp` wrapper + optional `AdminModel.totp_secret`)
138
- - **Multi-tenant scope** — `TenantScopedRepository(tenant_id)` auto-injects `WHERE tenant_id = …` on every query
139
- - **`SlowQueryLogger`** SQLAlchemy event logging queries > N ms with EXPLAIN
140
- - **`AlembicHelper.safe_upgrade()`** block destructive migrations (DROP COLUMN/TABLE) without `--force`
141
- - **Graceful shutdown** drain in-flight requests on SIGTERM before uvicorn dies
142
- - **`make_websocket_router`**bearer auth, heartbeat, broadcast (today SSE only)
143
- - **CLI:** `tempest db seed`, `tempest user create-admin`, `tempest secrets rotate`
144
-
145
- ### Planned release cadence
146
-
147
- - **v0.23.0 observability + retries** (high return, low cost)
148
- - `setup_tracing(app, otlp_endpoint=…)` with OTel auto-instrumentation
149
- - `HTTPClient` (typed httpx wrapper)
150
- - Prometheus `/metrics` endpoint
151
- - **v0.24.0 — cloud uploads + idempotency** (unblocks multi-replica deploys)
152
- - `UploadUtils` with pluggable backends + `S3Backend`
153
- - `IdempotencyMiddleware` + `idempotency_keys` table on `BaseModel`
154
- - `EmailUtils.render_template`
155
-
156
- Anything beyond v0.24.0 is bumped from the roadmap when business
157
- pressure picks the next item — keep the roadmap honest, not
158
- aspirational.
113
+ `tempest db init/revision/upgrade/downgrade/current/history/seed`,
114
+ `tempest user create [--admin] / list`, `tempest secrets rotate`,
115
+ plus quality gates (`lint`, `fix`, `format`, `fmt-check`, `type`,
116
+ `test`, `check`).
117
+
118
+ The whole Tier S / Tier A / Tier B backlog that used to live here is
119
+ **shipped** idempotency, cloud uploads, OTel tracing, `HTTPClient`,
120
+ the outbox pattern, `EmailUtils.render_template`, OAuth2/OIDC, CSRF +
121
+ body-size middleware, bulk repo ops, the Prometheus endpoint, TOTP/MFA,
122
+ `TenantScopedRepository`, `SlowQueryLogger`, `AlembicHelper.safe_upgrade`,
123
+ graceful shutdown, `make_websocket_router`, and the `db seed` /
124
+ `secrets rotate` CLI commands all exist today. The covers list above is
125
+ the source of truth; don't re-plan finished work.
126
+
127
+ ### Next-version plan
128
+
129
+ Ordered by the priority the user set. Each item is the next minor
130
+ bump (`feat: vX.Y+1.0`) when picked up. Keep this honest — move an
131
+ item up to the covers list the moment it ships, and only add a new
132
+ entry when business pressure actually selects it.
133
+
134
+ 1. **Rate-limit per user / scope** — `RateLimitMiddleware` keys on
135
+ client IP today. Add a pluggable key extractor (authenticated
136
+ user id, API key, tenant, or a custom callable) so limits can be
137
+ per-principal instead of per-IP, with the same memory + Redis
138
+ backends. Shared NAT / proxy clients stop sharing one bucket.
139
+ 2. **i18n / localized `AppException` messages**error envelopes are
140
+ English-only. Let each `AppException` carry a message key + params
141
+ resolved against per-locale catalogs (PT-BR default + EN), picked
142
+ from `Accept-Language` or an explicit override, so `register_
143
+ exception_handlers` emits the localized `message` without callers
144
+ hand-translating.
145
+ 3. **`@cached` tag / namespace invalidation** the cache decorator
146
+ only expires by TTL. Add tag/namespace tagging on write plus an
147
+ `invalidate(tag|namespace)` call so a mutation can drop every
148
+ dependent entry at once instead of waiting out the TTL.
149
+ 4. **Feature flags** `FeatureFlag` with env + Redis backends
150
+ (Redis for runtime toggles, env for static), a dependency/guard to
151
+ gate routes and a helper to branch in services, so rollouts and
152
+ kill-switches don't require a redeploy.
153
+ 5. **Audit trail** — beyond `AuditMixin` (timestamps), a per-entity
154
+ mutation log capturing actor, action, before/after diff on
155
+ create/update/delete, written in the same transaction as the
156
+ change (reuse the outbox machinery), with a `BaseRepository` hook
157
+ so services opt in per model.
159
158
 
160
159
  ## Conventions specific to this repo
161
160
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tempest-fastapi-sdk
3
- Version: 0.53.0
3
+ Version: 0.55.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
@@ -239,9 +239,9 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
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
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
- | `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException` |
242
+ | `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException`, i18n (`MessageCatalog`, `default_message_catalog`, `parse_accept_language`, `DEFAULT_LOCALE`) |
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`, `GracefulShutdownMiddleware`, `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` (+ `RateLimitStore`/`MemoryRateLimitStore`/`RedisRateLimitStore`/`RateLimitResult` and `key_by_ip`/`key_by_jwt_subject`/`key_by_jwt_claim`/`key_by_header`), `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` |
@@ -2994,7 +2994,7 @@ Tweak `page_param=` / `size_param=` when your service uses non-standard query pa
2994
2994
 
2995
2995
  ### Rate limit middleware recipe
2996
2996
 
2997
- `RateLimitMiddleware` is a lightweight in-process sliding-window limiter — each unique key (client IP by default) is allowed at most `max_requests` requests inside every `window_seconds` window. Exceeded requests get a `429 Too Many Requests` with a `Retry-After` header.
2997
+ `RateLimitMiddleware` is a sliding-window limiter — each unique key (client IP by default) is allowed at most `max_requests` requests inside every `window_seconds` window. Exceeded requests get a `429 Too Many Requests` with a `Retry-After` header. Two axes are pluggable: the **store** (memory or Redis) and the **key** (IP, user, tenant, API key).
2998
2998
 
2999
2999
  ```python
3000
3000
  # src/api/app.py
@@ -3012,35 +3012,53 @@ def create_app() -> FastAPI:
3012
3012
  ...
3013
3013
  ```
3014
3014
 
3015
- Pass `key_func=` to partition state by tenant header, authenticated user, or any request attribute. The full app factory then looks like:
3015
+ **Limit per principal.** By default the key is the client IP. The
3016
+ `key_by_*` factories key on the authenticated user, an arbitrary token
3017
+ claim or a header instead. Because the middleware runs *before* FastAPI
3018
+ dependencies resolve, the `key_by_jwt_*` factories decode the bearer
3019
+ from the raw request (`decode_or_none`) and fall back to the IP for
3020
+ anonymous traffic:
3021
+
3022
+ | Factory | Key produced | Use |
3023
+ | --- | --- | --- |
3024
+ | `key_by_ip(trusted_header=...)` | `ip:<addr>` | Per IP (default). |
3025
+ | `key_by_jwt_subject(jwt)` | `user:<sub>` | Per authenticated user. |
3026
+ | `key_by_jwt_claim(jwt, "tenant_id", scope="tenant")` | `tenant:<id>` | Per token claim. |
3027
+ | `key_by_header("x-api-key", scope="apikey")` | `apikey:<value>` | Per header value. |
3028
+
3029
+ **Share state across replicas.** The default `MemoryRateLimitStore`
3030
+ counts in-process (single worker). Pass `RedisRateLimitStore(redis)` for
3031
+ multi-replica deploys — an atomic Lua sliding-window log over a sorted
3032
+ set, `fail_open=True` by default:
3016
3033
 
3017
3034
  ```python
3018
3035
  # src/api/app.py
3019
- from fastapi import FastAPI, Request
3020
-
3021
- from tempest_fastapi_sdk import RateLimitMiddleware
3036
+ from redis.asyncio import Redis
3022
3037
 
3038
+ from tempest_fastapi_sdk import (
3039
+ RateLimitMiddleware,
3040
+ RedisRateLimitStore,
3041
+ key_by_jwt_subject,
3042
+ )
3023
3043
 
3024
- def by_tenant(request: Request) -> str:
3025
- """Bucket every request under its tenant header, falling back to IP."""
3026
- return request.headers.get("X-Tenant", request.client.host or "anon")
3044
+ from src.api.dependencies.resources import get_jwt_utils
3027
3045
 
3028
3046
 
3029
3047
  def create_app() -> FastAPI:
3048
+ redis: Redis = Redis.from_url("redis://localhost:6379/0")
3030
3049
  app = FastAPI(...)
3031
3050
  app.add_middleware(
3032
3051
  RateLimitMiddleware,
3033
3052
  max_requests=600,
3034
3053
  window_seconds=60.0,
3035
- key_func=by_tenant, # ← swap the default IP key
3054
+ key_func=key_by_jwt_subject(get_jwt_utils()), # ← per-user buckets
3055
+ store=RedisRateLimitStore(redis), # ← shared across replicas
3036
3056
  exempt_paths=("/health/liveness", "/health/readiness"),
3037
3057
  )
3038
3058
  return app
3039
3059
  ```
3040
3060
 
3041
- The two highlighted pieces the `by_tenant` helper and the `key_func=by_tenant` wiring are the only diff against the default snippet above.
3042
-
3043
- The state is held **in-process** — for multi-worker deployments either run a single uvicorn worker behind a single reverse-proxy node, or push rate limiting to the edge (nginx / Cloudflare / AWS WAF). The middleware is intentionally simple; a Redis-backed sliding-window limiter is one issue away if it shows up as a real need.
3061
+ The sliding-window semantics are identical across both stores; only where the counters live changes. You can still push rate limiting to the edge (nginx / Cloudflare / AWS WAF) when you prefer.
3044
3062
 
3045
3063
  ### Outbox dispatcher pattern recipe
3046
3064
 
@@ -137,9 +137,9 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
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
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
- | `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException` |
140
+ | `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException`, i18n (`MessageCatalog`, `default_message_catalog`, `parse_accept_language`, `DEFAULT_LOCALE`) |
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`, `GracefulShutdownMiddleware`, `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` (+ `RateLimitStore`/`MemoryRateLimitStore`/`RedisRateLimitStore`/`RateLimitResult` and `key_by_ip`/`key_by_jwt_subject`/`key_by_jwt_claim`/`key_by_header`), `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` |
@@ -2892,7 +2892,7 @@ Tweak `page_param=` / `size_param=` when your service uses non-standard query pa
2892
2892
 
2893
2893
  ### Rate limit middleware recipe
2894
2894
 
2895
- `RateLimitMiddleware` is a lightweight in-process sliding-window limiter — each unique key (client IP by default) is allowed at most `max_requests` requests inside every `window_seconds` window. Exceeded requests get a `429 Too Many Requests` with a `Retry-After` header.
2895
+ `RateLimitMiddleware` is a sliding-window limiter — each unique key (client IP by default) is allowed at most `max_requests` requests inside every `window_seconds` window. Exceeded requests get a `429 Too Many Requests` with a `Retry-After` header. Two axes are pluggable: the **store** (memory or Redis) and the **key** (IP, user, tenant, API key).
2896
2896
 
2897
2897
  ```python
2898
2898
  # src/api/app.py
@@ -2910,35 +2910,53 @@ def create_app() -> FastAPI:
2910
2910
  ...
2911
2911
  ```
2912
2912
 
2913
- Pass `key_func=` to partition state by tenant header, authenticated user, or any request attribute. The full app factory then looks like:
2913
+ **Limit per principal.** By default the key is the client IP. The
2914
+ `key_by_*` factories key on the authenticated user, an arbitrary token
2915
+ claim or a header instead. Because the middleware runs *before* FastAPI
2916
+ dependencies resolve, the `key_by_jwt_*` factories decode the bearer
2917
+ from the raw request (`decode_or_none`) and fall back to the IP for
2918
+ anonymous traffic:
2919
+
2920
+ | Factory | Key produced | Use |
2921
+ | --- | --- | --- |
2922
+ | `key_by_ip(trusted_header=...)` | `ip:<addr>` | Per IP (default). |
2923
+ | `key_by_jwt_subject(jwt)` | `user:<sub>` | Per authenticated user. |
2924
+ | `key_by_jwt_claim(jwt, "tenant_id", scope="tenant")` | `tenant:<id>` | Per token claim. |
2925
+ | `key_by_header("x-api-key", scope="apikey")` | `apikey:<value>` | Per header value. |
2926
+
2927
+ **Share state across replicas.** The default `MemoryRateLimitStore`
2928
+ counts in-process (single worker). Pass `RedisRateLimitStore(redis)` for
2929
+ multi-replica deploys — an atomic Lua sliding-window log over a sorted
2930
+ set, `fail_open=True` by default:
2914
2931
 
2915
2932
  ```python
2916
2933
  # src/api/app.py
2917
- from fastapi import FastAPI, Request
2918
-
2919
- from tempest_fastapi_sdk import RateLimitMiddleware
2934
+ from redis.asyncio import Redis
2920
2935
 
2936
+ from tempest_fastapi_sdk import (
2937
+ RateLimitMiddleware,
2938
+ RedisRateLimitStore,
2939
+ key_by_jwt_subject,
2940
+ )
2921
2941
 
2922
- def by_tenant(request: Request) -> str:
2923
- """Bucket every request under its tenant header, falling back to IP."""
2924
- return request.headers.get("X-Tenant", request.client.host or "anon")
2942
+ from src.api.dependencies.resources import get_jwt_utils
2925
2943
 
2926
2944
 
2927
2945
  def create_app() -> FastAPI:
2946
+ redis: Redis = Redis.from_url("redis://localhost:6379/0")
2928
2947
  app = FastAPI(...)
2929
2948
  app.add_middleware(
2930
2949
  RateLimitMiddleware,
2931
2950
  max_requests=600,
2932
2951
  window_seconds=60.0,
2933
- key_func=by_tenant, # ← swap the default IP key
2952
+ key_func=key_by_jwt_subject(get_jwt_utils()), # ← per-user buckets
2953
+ store=RedisRateLimitStore(redis), # ← shared across replicas
2934
2954
  exempt_paths=("/health/liveness", "/health/readiness"),
2935
2955
  )
2936
2956
  return app
2937
2957
  ```
2938
2958
 
2939
- The two highlighted pieces the `by_tenant` helper and the `key_func=by_tenant` wiring are the only diff against the default snippet above.
2940
-
2941
- The state is held **in-process** — for multi-worker deployments either run a single uvicorn worker behind a single reverse-proxy node, or push rate limiting to the edge (nginx / Cloudflare / AWS WAF). The middleware is intentionally simple; a Redis-backed sliding-window limiter is one issue away if it shows up as a real need.
2959
+ The sliding-window semantics are identical across both stores; only where the counters live changes. You can still push rate limiting to the edge (nginx / Cloudflare / AWS WAF) when you prefer.
2942
2960
 
2943
2961
  ### Outbox dispatcher pattern recipe
2944
2962
 
@@ -102,6 +102,73 @@ Key points:
102
102
  - `make_token_dependency(secret)` returns an async dependency that validates `X-Token` via `hmac.compare_digest`; pass an empty string to disable in dev. The dependency lives next to the rest of the auth glue in `src/api/dependencies/auth.py` once it grows beyond the one-liner above.
103
103
 
104
104
 
105
+ ### Localized error messages (i18n)
106
+
107
+ By default the envelope `detail` is the exception's literal message (English for the built-ins). To return the message **in the client's language** without translating at each `raise`, pass a `MessageCatalog` to `register_exception_handlers`:
108
+
109
+ ```python
110
+ # src/api/app.py
111
+ from tempest_fastapi_sdk import default_message_catalog, register_exception_handlers
112
+
113
+
114
+ def create_app() -> FastAPI:
115
+ app = FastAPI(...)
116
+ register_exception_handlers(
117
+ app,
118
+ catalog=default_message_catalog(), # ← bundled PT-BR + EN-US
119
+ default_locale="pt-BR",
120
+ )
121
+ ...
122
+ ```
123
+
124
+ The handler negotiates the locale from the `Accept-Language` header (ordered by `q`), falls back to `default_locale` when nothing matches, and resolves the exception **key** — `message_key` if set, else the `code` — against the catalog. With no catalog, or when the key is unknown, the literal `detail` is kept (zero breakage).
125
+
126
+ ```python
127
+ # Same NotFoundException, language decided by the client's Accept-Language:
128
+ # Accept-Language: pt-BR → {"detail": "Recurso não encontrado", "code": "NOT_FOUND"}
129
+ # Accept-Language: en-US → {"detail": "Resource not found", "code": "NOT_FOUND"}
130
+ ```
131
+
132
+ For domain codes (and messages with parameters), extend the catalog with `merge` and pass `message_params` at the `raise`:
133
+
134
+ ```python
135
+ # src/core/i18n.py
136
+ from tempest_fastapi_sdk import MessageCatalog, default_message_catalog
137
+
138
+ CATALOG: MessageCatalog = default_message_catalog().merge(
139
+ {
140
+ "pt-BR": {"USER_NOT_FOUND": "Usuário {email} não encontrado"},
141
+ "en-US": {"USER_NOT_FOUND": "User {email} not found"},
142
+ }
143
+ )
144
+ ```
145
+
146
+ ```python
147
+ # src/services/user.py
148
+ from tempest_fastapi_sdk import NotFoundException
149
+
150
+
151
+ def require_user(email: str) -> None:
152
+ """Raise a localized 404 carrying the offending e-mail.
153
+
154
+ Args:
155
+ email (str): The e-mail that was not found.
156
+
157
+ Raises:
158
+ NotFoundException: Always — keyed to ``USER_NOT_FOUND`` so the
159
+ handler localizes it from the request locale.
160
+ """
161
+ raise NotFoundException(
162
+ "User not found", # literal fallback
163
+ code="USER_NOT_FOUND",
164
+ message_params={"email": email},
165
+ )
166
+ ```
167
+
168
+ !!! tip "The key defaults to the `code`"
169
+ You rarely pass `message_key` — it falls back to the exception's `code`. Set `message_key` only to decouple the translated string from the error code. A template referencing a missing param is returned uninterpolated rather than raising.
170
+
171
+
105
172
  ## JWT bearer / current-user / role dependencies
106
173
 
107
174
 
@@ -193,7 +260,7 @@ async def update_perms(user_id: UUID) -> None:
193
260
  ## Rate limit middleware
194
261
 
195
262
 
196
- `RateLimitMiddleware` is a lightweight in-process sliding-window limiter — each unique key (client IP by default) is allowed at most `max_requests` requests inside every `window_seconds` window. Exceeded requests get a `429 Too Many Requests` with a `Retry-After` header.
263
+ `RateLimitMiddleware` is a sliding-window limiter — each unique key (client IP by default) is allowed at most `max_requests` requests inside every `window_seconds` window. Exceeded requests get a `429 Too Many Requests` with a `Retry-After` header. Two axes are pluggable: the **store** (memory or Redis) and the **key** (IP, user, tenant, API key) — see below.
197
264
 
198
265
  ```python
199
266
  # src/api/app.py
@@ -211,38 +278,73 @@ def create_app() -> FastAPI:
211
278
  ...
212
279
  ```
213
280
 
214
- Pass `key_func=` to partition state by tenant header, authenticated user, or any request attribute. The full app factory then looks like:
281
+ ### Limit per user / tenant / API key
282
+
283
+ By default the key is the client IP. To limit **per principal** (authenticated user, tenant, API key), pass a `key_func`. The SDK ships ready-made factories:
284
+
285
+ | Factory | Key produced | Use |
286
+ | --- | --- | --- |
287
+ | `key_by_ip(trusted_header=...)` | `ip:<addr>` | Per IP (default). |
288
+ | `key_by_jwt_subject(jwt)` | `user:<sub>` | Per authenticated user (`sub` claim). |
289
+ | `key_by_jwt_claim(jwt, "tenant_id", scope="tenant")` | `tenant:<id>` | Per arbitrary token claim. |
290
+ | `key_by_header("x-api-key", scope="apikey")` | `apikey:<value>` | Per header value. |
291
+
292
+ !!! warning "The middleware runs before dependencies"
293
+ `RateLimitMiddleware` runs **before** FastAPI `Depends` resolve — so the user authenticated by your auth dependency does not exist yet when the key is computed. That is why the `key_by_jwt_*` factories decode the bearer **from the raw request** (via `JWTUtils.decode_or_none`, no exception raised). Anonymous traffic falls back to the IP, so it stays limited.
215
294
 
216
295
  ```python
217
296
  # src/api/app.py
218
- from fastapi import FastAPI, Request
297
+ from fastapi import FastAPI
219
298
 
220
- from tempest_fastapi_sdk import RateLimitMiddleware
299
+ from tempest_fastapi_sdk import RateLimitMiddleware, key_by_jwt_subject
300
+
301
+ from src.api.dependencies.resources import get_jwt_utils
221
302
 
222
303
 
223
- def by_tenant(request: Request) -> str:
224
- """Bucket every request under its tenant header, falling back to IP."""
225
- return request.headers.get(
226
- "X-Tenant",
227
- request.client.host if request.client else "anon",
304
+ def create_app() -> FastAPI:
305
+ app = FastAPI(...)
306
+ app.add_middleware(
307
+ RateLimitMiddleware,
308
+ max_requests=600,
309
+ window_seconds=60.0,
310
+ key_func=key_by_jwt_subject(get_jwt_utils()), # ← limit per user
311
+ exempt_paths=("/health/liveness", "/health/readiness"),
228
312
  )
313
+ return app
314
+ ```
315
+
316
+ ### Distributed state with Redis
317
+
318
+ The default store (`MemoryRateLimitStore`) counts **in-process** — correct for a single worker. For multi-replica deployments, pass `store=RedisRateLimitStore(redis)`: each key becomes a sorted set and a single Lua script prunes expired members, counts, and adds the new hit **atomically** (no race between count and add). On a Redis error, `fail_open=True` (default) allows the request rather than locking everyone out.
319
+
320
+ ```python
321
+ # src/api/app.py
322
+ from redis.asyncio import Redis
323
+
324
+ from tempest_fastapi_sdk import (
325
+ RateLimitMiddleware,
326
+ RedisRateLimitStore,
327
+ key_by_jwt_subject,
328
+ )
329
+
330
+ from src.api.dependencies.resources import get_jwt_utils
229
331
 
230
332
 
231
333
  def create_app() -> FastAPI:
334
+ redis: Redis = Redis.from_url("redis://localhost:6379/0")
232
335
  app = FastAPI(...)
233
336
  app.add_middleware(
234
337
  RateLimitMiddleware,
235
338
  max_requests=600,
236
339
  window_seconds=60.0,
237
- key_func=by_tenant, # ← swap the default IP key
340
+ key_func=key_by_jwt_subject(get_jwt_utils()),
341
+ store=RedisRateLimitStore(redis), # ← shared across replicas
238
342
  exempt_paths=("/health/liveness", "/health/readiness"),
239
343
  )
240
344
  return app
241
345
  ```
242
346
 
243
- The two highlighted pieces the `by_tenant` helper and the `key_func=by_tenant` wiring are the only diff against the default snippet above.
244
-
245
- The state is held **in-process** — for multi-worker deployments either run a single uvicorn worker behind a single reverse-proxy node, or push rate limiting to the edge (nginx / Cloudflare / AWS WAF). The middleware is intentionally simple; a Redis-backed sliding-window limiter is one issue away if it shows up as a real need.
347
+ The sliding-window semantics are identical across both stores; only where the counters live changes. You can still push rate limiting to the edge (nginx / Cloudflare / AWS WAF) when you prefer.
246
348
 
247
349
 
248
350
  ## Webhook signature verification