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.
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/CHANGELOG.md +56 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/CLAUDE.md +52 -53
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/PKG-INFO +33 -15
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/README.md +32 -14
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http.en.md +115 -13
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http.md +115 -13
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/reference.md +12 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/pyproject.toml +1 -1
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/__init__.py +25 -1
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/__init__.py +16 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/handlers.py +43 -2
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +19 -1
- tempest_fastapi_sdk-0.55.0/tempest_fastapi_sdk/api/middlewares/rate_limit.py +553 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/__init__.py +10 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/base.py +26 -1
- tempest_fastapi_sdk-0.55.0/tempest_fastapi_sdk/exceptions/i18n.py +231 -0
- tempest_fastapi_sdk-0.55.0/tests/api/test_rate_limit_extras.py +287 -0
- tempest_fastapi_sdk-0.55.0/tests/exceptions/test_i18n.py +209 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/uv.lock +1 -1
- tempest_fastapi_sdk-0.53.0/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -142
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.github/workflows/ci.yml +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.github/workflows/docs.yml +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.gitignore +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/.python-version +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/Makefile +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/architecture.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/architecture.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/changelog.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/changelog.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/contributing.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/contributing.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/index.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/index.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/installation.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/installation.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/index.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/index.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/api.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/api.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/business-rules.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/business-rules.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/domain.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/domain.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/flows.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/flows.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/index.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/learning/marketplace/index.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/migration.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/migration.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/admin.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/admin.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/auth-flow.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/auth-flow.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/br-helpers.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/br-helpers.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cache.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cache.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cli.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/cli.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/database.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/database.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/deploy-safety.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/deploy-safety.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/downloads.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/downloads.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/email.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/email.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http-client.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/http-client.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/idempotency.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/idempotency.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/index.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/index.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/logging.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/logging.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/metrics.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/metrics.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/mfa.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/mfa.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/multi-tenant.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/multi-tenant.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/observability.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/observability.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/offline-sync.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/offline-sync.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/outbox.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/outbox.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/queue-tasks.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/queue-tasks.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/realtime.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/realtime.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/security.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/security.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/sessions.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/sessions.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/storage.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/storage.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/testing.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/testing.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/uploads.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/uploads.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/utilities.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/utilities.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/webpush.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/webpush.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/websocket.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/recipes/websocket.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/reference.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/roadmap.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/roadmap.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/tutorial.en.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/docs/tutorial.md +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/mkdocs.yml +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/scripts/extract_recipe.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/config.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/session.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/site.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/server.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/static.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/guards.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/service.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/activation.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/auth/templates/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
- {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
- {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
- {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
- {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
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
- {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
- {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
- {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
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/db.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/main.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/new.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/cli/user.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/context.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/enums.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/core/logging.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/connection.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/model.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/repository.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/py.typed +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/services/base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/settings/base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/testing/database.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/data/br_locations.json +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/download.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/email.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/locations.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/log.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/password.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_auth.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_forms.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_logs_nav.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_site.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/admin/test_user_model.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_body_size.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_cookies.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_cors.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_csrf.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_dependencies_auth.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_graceful.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_handlers.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_health_router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_idempotency.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_jwt_dependency.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_logs_router.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_oauth.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_prometheus.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_request_id_middleware.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_role_dependency.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_server.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_static.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_tracing.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_webhooks.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/api/test_webhooks_rsa.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/test_guards.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/auth/test_service.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cache/test_decorator.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cache/test_redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_db.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_db_seed.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_generate.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_main.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_secrets.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/cli/test_user.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/conftest.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/controllers/test_base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/test_context.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/test_enums.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/core/test_logging.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_bulk_ops.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_connection.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_migrations.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_model.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_outbox.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_repository.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_safe_upgrade.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_slow_query.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/db/test_tenant.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/exceptions/test_exceptions.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/queue/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_cursor_pagination.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_link_headers.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_pagination.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/schemas/test_response.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/services/test_base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sessions/test_sessions.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/settings/test_base.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/settings/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/sse/test_event_stream.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/storage/test_minio_client.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/tasks/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/tasks/test_scheduler.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/testing/test_database.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_client_ip.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_datetime.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_dict.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_download.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_email.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_http_client.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_jwt.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_lazy_extras.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_locations.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_log.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_metrics.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_password.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_regex.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_throttle.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/utils/test_upload.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/webpush/test_dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/webpush/test_schemas.py +0 -0
- {tempest_fastapi_sdk-0.53.0 → tempest_fastapi_sdk-0.55.0}/tests/websockets/__init__.py +0 -0
- {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`,
|
|
108
|
-
(`lint`, `fix`, `format`, `fmt-check`, `type`,
|
|
109
|
-
`check`).
|
|
110
|
-
|
|
111
|
-
The
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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=
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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=
|
|
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
|
|
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
|