tempest-fastapi-sdk 0.45.0__tar.gz → 0.47.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.45.0 → tempest_fastapi_sdk-0.47.0}/CHANGELOG.md +51 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/PKG-INFO +18 -3
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/README.md +17 -2
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/cli.en.md +42 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/cli.md +42 -0
- tempest_fastapi_sdk-0.47.0/docs/recipes/deploy-safety.en.md +111 -0
- tempest_fastapi_sdk-0.47.0/docs/recipes/deploy-safety.md +110 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/index.en.md +2 -1
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/index.md +2 -1
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/reference.md +2 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/mkdocs.yml +2 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/pyproject.toml +1 -1
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/__init__.py +5 -1
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/__init__.py +2 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +2 -0
- tempest_fastapi_sdk-0.47.0/tempest_fastapi_sdk/api/middlewares/graceful.py +225 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/db.py +113 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/main.py +2 -0
- tempest_fastapi_sdk-0.47.0/tempest_fastapi_sdk/cli/secrets.py +159 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/__init__.py +4 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/migrations.py +116 -0
- tempest_fastapi_sdk-0.47.0/tests/api/test_graceful.py +78 -0
- tempest_fastapi_sdk-0.47.0/tests/cli/test_db_seed.py +107 -0
- tempest_fastapi_sdk-0.47.0/tests/cli/test_secrets.py +78 -0
- tempest_fastapi_sdk-0.47.0/tests/db/test_safe_upgrade.py +98 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/uv.lock +1 -1
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/.github/workflows/ci.yml +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/.github/workflows/docs.yml +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/.gitignore +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/.python-version +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/CLAUDE.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/Makefile +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/architecture.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/architecture.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/changelog.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/changelog.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/contributing.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/contributing.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/index.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/index.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/installation.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/installation.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/index.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/index.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/api.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/api.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/business-rules.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/business-rules.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/domain.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/domain.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/flows.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/flows.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/index.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/learning/marketplace/index.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/migration.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/migration.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/admin.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/admin.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/auth-flow.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/auth-flow.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/br-helpers.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/br-helpers.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/cache.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/cache.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/database.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/database.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/downloads.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/downloads.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/email.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/email.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/http-client.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/http-client.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/http.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/http.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/idempotency.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/idempotency.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/logging.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/logging.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/metrics.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/metrics.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/mfa.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/mfa.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/multi-tenant.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/multi-tenant.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/observability.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/observability.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/outbox.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/outbox.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/queue-tasks.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/queue-tasks.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/realtime.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/realtime.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/security.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/security.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/sessions.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/sessions.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/storage.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/storage.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/testing.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/testing.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/uploads.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/uploads.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/utilities.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/utilities.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/webpush.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/webpush.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/websocket.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/recipes/websocket.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/reference.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/roadmap.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/roadmap.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/tutorial.en.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/docs/tutorial.md +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/scripts/extract_recipe.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/config.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/session.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/site.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/server.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/static.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/service.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/activation.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/auth/templates/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/new.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/cli/user.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/core/context.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/core/enums.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/core/logging.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/connection.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/model.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/repository.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/py.typed +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/services/base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/settings/base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/testing/database.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/download.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/email.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/log.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/password.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_auth.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_forms.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_logs_nav.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_site.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/admin/test_user_model.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_body_size.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_cookies.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_cors.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_csrf.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_dependencies_auth.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_handlers.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_health_router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_idempotency.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_jwt_dependency.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_logs_router.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_oauth.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_prometheus.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_request_id_middleware.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_role_dependency.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_server.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_static.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_tracing.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_webhooks.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/api/test_webhooks_rsa.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/auth/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/auth/test_service.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cache/test_decorator.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cache/test_redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cli/test_db.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cli/test_docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cli/test_generate.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cli/test_main.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/cli/test_user.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/conftest.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/controllers/test_base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/core/test_context.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/core/test_enums.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/core/test_logging.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_bulk_ops.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_connection.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_migrations.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_model.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_outbox.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_repository.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_slow_query.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/db/test_tenant.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/exceptions/test_exceptions.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/queue/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/schemas/test_base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/schemas/test_cursor_pagination.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/schemas/test_link_headers.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/schemas/test_pagination.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/schemas/test_response.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/services/test_base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/sessions/test_sessions.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/settings/test_base.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/settings/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/sse/test_event_stream.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/storage/test_minio_client.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/tasks/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/tasks/test_scheduler.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/testing/test_database.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_client_ip.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_datetime.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_dict.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_download.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_email.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_http_client.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_jwt.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_lazy_extras.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_log.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_metrics.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_password.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_regex.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_throttle.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/utils/test_upload.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/webpush/test_dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/webpush/test_schemas.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tests/websockets/test_hub_and_router.py +0 -0
|
@@ -5,6 +5,57 @@ 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.47.0] — 2026-06-11
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`tempest db seed`** — runs a project seed callable
|
|
13
|
+
(default `src.db.seeds:seed`, dotted `module:callable`, sync or async,
|
|
14
|
+
taking one `AsyncSession`) inside a managed session: commit on
|
|
15
|
+
success, rollback on error. The SDK only wires the session lifecycle;
|
|
16
|
+
the callable owns what gets inserted. Prints the row count when the
|
|
17
|
+
callable returns an `int`.
|
|
18
|
+
- **`tempest secrets rotate`** — generates fresh URL-safe secrets for
|
|
19
|
+
the keys a service signs/authenticates with (`JWT_SECRET` /
|
|
20
|
+
`TOKEN_SECRET` by default; override with `--keys`) and rewrites the
|
|
21
|
+
matching `.env` lines **in place** (existing keys replaced, missing
|
|
22
|
+
keys appended) after a `.env.bak` backup. `--print` writes nothing and
|
|
23
|
+
emits the values to stdout; `--length` sets the entropy; `--no-backup`
|
|
24
|
+
skips the backup.
|
|
25
|
+
|
|
26
|
+
### Docs
|
|
27
|
+
|
|
28
|
+
- CLI recipe (bilingual) gains **`db seed`** and **`secrets rotate`**
|
|
29
|
+
sections; README CLI section and recipes index updated.
|
|
30
|
+
|
|
31
|
+
## [0.46.0] — 2026-06-11
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **`AlembicHelper.safe_upgrade(revision="head", *, force=False)`** —
|
|
36
|
+
runs the upgrade only after scanning each pending migration's
|
|
37
|
+
`upgrade()` for data-destroying calls (`op.drop_table` /
|
|
38
|
+
`op.drop_column` / `op.drop_constraint` and `batch_op` variants). When
|
|
39
|
+
any are found it raises **`DestructiveMigrationError`** (carrying the
|
|
40
|
+
offending `(revision, operation)` pairs) and leaves the database
|
|
41
|
+
untouched; `force=True` logs and proceeds. Source-based scanning is
|
|
42
|
+
dialect-agnostic (no false positives on SQLite batch rebuilds) and
|
|
43
|
+
ignores drops in `downgrade()`. `pending_destructive_ops()` exposes the
|
|
44
|
+
scan without running anything (CI-friendly).
|
|
45
|
+
- **`GracefulShutdownMiddleware`** — tracks in-flight requests and, once
|
|
46
|
+
draining, replies `503` + `Retry-After` to new requests so a load
|
|
47
|
+
balancer deregisters the instance. `begin_drain()` / `wait_drained()`
|
|
48
|
+
(bounded by `drain_timeout`) are driven from the lifespan shutdown
|
|
49
|
+
(uvicorn owns `SIGTERM`); an opt-in `install_signal_handlers()` chains
|
|
50
|
+
the previous handler for servers that manage signals themselves. Wired
|
|
51
|
+
via `app.add_middleware(BaseHTTPMiddleware, dispatch=shutdown.dispatch)`.
|
|
52
|
+
|
|
53
|
+
### Docs
|
|
54
|
+
|
|
55
|
+
- New bilingual recipe **Deploy seguro / Safe deploys** covering
|
|
56
|
+
`safe_upgrade` and `GracefulShutdownMiddleware`, added to the nav, the
|
|
57
|
+
recipes index, and the API reference.
|
|
58
|
+
|
|
8
59
|
## [0.45.0] — 2026-06-11
|
|
9
60
|
|
|
10
61
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempest-fastapi-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.47.0
|
|
4
4
|
Summary: Shared FastAPI building blocks: base schemas, ORM model, async repository, exceptions, pagination and settings — the conventions used across Tempest projects.
|
|
5
5
|
Project-URL: Homepage, https://github.com/mauriciobenjamin700/tempest-fastapi-sdk
|
|
6
6
|
Project-URL: Repository, https://github.com/mauriciobenjamin700/tempest-fastapi-sdk
|
|
@@ -238,10 +238,10 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
|
|
|
238
238
|
| Module | Exports |
|
|
239
239
|
| --- | --- |
|
|
240
240
|
| `tempest_fastapi_sdk.schemas` | `BaseSchema`, `BaseResponseSchema`, `BasePaginationFilterSchema`, `BasePaginationSchema[T]`, `CursorPaginationFilterSchema`, `CursorPaginationSchema`, `LogEntrySchema`, `encode_cursor`, `decode_cursor`, `build_pagination_link_header` |
|
|
241
|
-
| `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `TenantScopedRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
|
|
241
|
+
| `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `TenantScopedRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper` (+ `safe_upgrade`), `DestructiveMigrationError`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
|
|
242
242
|
| `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException` |
|
|
243
243
|
| `tempest_fastapi_sdk.settings` | `BaseAppSettings`, `ServerSettings`, `LogSettings`, `DatabaseSettings`, `RedisSettings`, `RabbitMQSettings`, `JWTSettings`, `CORSSettings`, `EmailSettings`, `UploadSettings`, `TokenSettings`, `WebPushSettings`, `TaskIQSettings`, `MinIOSettings`, `AuthSettings` |
|
|
244
|
-
| `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
|
|
244
|
+
| `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `GracefulShutdownMiddleware`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
|
|
245
245
|
| `tempest_fastapi_sdk.auth` *(extra: `[auth]`, opcional `[email]`, `[mfa]`)* | `UserAuthService`, `make_auth_router`, `SignupSchema` / `LoginSchema` / `PasswordResetRequestSchema` / `PasswordResetConfirmSchema` + responses, `ActivationToken`, `PasswordResetToken`, MFA schemas (`MFAEnrollResponseSchema` / `MFAConfirmSchema` / `MFAVerifySchema` / `MFADisableSchema`) — signup/activate/login/reset + TOTP 2FA out of the box |
|
|
246
246
|
| `tempest_fastapi_sdk.controllers` | `BaseController` |
|
|
247
247
|
| `tempest_fastapi_sdk.services` | `BaseService` |
|
|
@@ -3411,6 +3411,21 @@ tempest db upgrade <rev> # upgrade to a specific revisio
|
|
|
3411
3411
|
tempest db downgrade # roll back one step
|
|
3412
3412
|
tempest db current # print the applied revision
|
|
3413
3413
|
tempest db history -v # revisions newest → oldest, verbose
|
|
3414
|
+
tempest db seed # run src.db.seeds:seed in one session
|
|
3415
|
+
tempest db seed --seed src.db.fixtures:demo # custom seed callable
|
|
3416
|
+
```
|
|
3417
|
+
|
|
3418
|
+
`tempest db seed` runs a project seed callable (default `src.db.seeds:seed`, sync or async, taking one `AsyncSession`) inside a managed session — commit on success, rollback on error.
|
|
3419
|
+
|
|
3420
|
+
#### Secrets — `tempest secrets`
|
|
3421
|
+
|
|
3422
|
+
Generates and rotates application secrets (`JWT_SECRET` / `TOKEN_SECRET` by default), rewriting the matching `.env` lines in place after a `.env.bak` backup; `--print` writes nothing and emits the values to stdout.
|
|
3423
|
+
|
|
3424
|
+
```bash
|
|
3425
|
+
tempest secrets rotate # rotate JWT_SECRET + TOKEN_SECRET in .env
|
|
3426
|
+
tempest secrets rotate --print # just print, write nothing
|
|
3427
|
+
tempest secrets rotate --keys JWT_SECRET,SESSION_SECRET --env .env.prod
|
|
3428
|
+
tempest secrets rotate --length 64 --no-backup
|
|
3414
3429
|
```
|
|
3415
3430
|
|
|
3416
3431
|
#### Users — `tempest user`
|
|
@@ -136,10 +136,10 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
|
|
|
136
136
|
| Module | Exports |
|
|
137
137
|
| --- | --- |
|
|
138
138
|
| `tempest_fastapi_sdk.schemas` | `BaseSchema`, `BaseResponseSchema`, `BasePaginationFilterSchema`, `BasePaginationSchema[T]`, `CursorPaginationFilterSchema`, `CursorPaginationSchema`, `LogEntrySchema`, `encode_cursor`, `decode_cursor`, `build_pagination_link_header` |
|
|
139
|
-
| `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `TenantScopedRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
|
|
139
|
+
| `tempest_fastapi_sdk.db` | `BaseModel`, `BaseUserModel`, `BaseUserTokenModel`, `BaseUserRecoveryCodeModel`, `make_user_recovery_code_model`, `UserTokenPurpose`, `BaseRepository[ModelType]`, `TenantScopedRepository[ModelType]`, `AsyncDatabaseManager`, `AlembicHelper` (+ `safe_upgrade`), `DestructiveMigrationError`, `NAMING_CONVENTION`, `AuditMixin`, `SoftDeleteMixin`, `MFAMixin`, `BASE_COLUMN_ORDER`, `reorder_base_columns_first`, `compose_hooks`, `SlowQueryLogger`, `BaseOutboxModel`, `OutboxRelay`, `OutboxStatus`, `BaseRepository.save_with_outbox` |
|
|
140
140
|
| `tempest_fastapi_sdk.exceptions` | `AppException`, `NotFoundException`, `ConflictException`, `ValidationException`, `UnauthorizedException`, `ForbiddenException`, `InvalidTokenException`, `ExpiredTokenException`, `FileTooLargeException`, `InvalidFileTypeException`, `TooManyRequestsException` |
|
|
141
141
|
| `tempest_fastapi_sdk.settings` | `BaseAppSettings`, `ServerSettings`, `LogSettings`, `DatabaseSettings`, `RedisSettings`, `RabbitMQSettings`, `JWTSettings`, `CORSSettings`, `EmailSettings`, `UploadSettings`, `TokenSettings`, `WebPushSettings`, `TaskIQSettings`, `MinIOSettings`, `AuthSettings` |
|
|
142
|
-
| `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
|
|
142
|
+
| `tempest_fastapi_sdk.api` | `register_exception_handlers`, `app_exception_handler`, `apply_cors`, `make_health_router`, `make_logs_router`, `make_prometheus_router`, `make_prometheus_registry`, `PrometheusMiddleware`, `LogSource`, `make_tool_spec_router`, `make_token_dependency`, `make_bearer_token_dependency`, `make_jwt_user_dependency`, `make_role_dependency`, `make_permission_dependency`, `require_x_token`, `run_server`, `RequestIDMiddleware`, `RateLimitMiddleware`, `IdempotencyMiddleware`, `MemoryIdempotencyStore`, `RedisIdempotencyStore`, `BodySizeLimitMiddleware`, `CSRFMiddleware`, `make_csrf_token_dependency`, `GracefulShutdownMiddleware`, `WebhookSignatureVerifier`, `RSAWebhookSignatureVerifier`, OAuth2 (`GoogleOAuthClient`, `GitHubOAuthClient`, `OIDCProvider`), `HardenedStaticFiles`, `DEFAULT_STATIC_SECURITY_HEADERS`, `set_cookie`, `clear_cookie`, `SameSite`, `HealthCheck`, `setup_tracing` *(extra: `[otel]`)* |
|
|
143
143
|
| `tempest_fastapi_sdk.auth` *(extra: `[auth]`, opcional `[email]`, `[mfa]`)* | `UserAuthService`, `make_auth_router`, `SignupSchema` / `LoginSchema` / `PasswordResetRequestSchema` / `PasswordResetConfirmSchema` + responses, `ActivationToken`, `PasswordResetToken`, MFA schemas (`MFAEnrollResponseSchema` / `MFAConfirmSchema` / `MFAVerifySchema` / `MFADisableSchema`) — signup/activate/login/reset + TOTP 2FA out of the box |
|
|
144
144
|
| `tempest_fastapi_sdk.controllers` | `BaseController` |
|
|
145
145
|
| `tempest_fastapi_sdk.services` | `BaseService` |
|
|
@@ -3309,6 +3309,21 @@ tempest db upgrade <rev> # upgrade to a specific revisio
|
|
|
3309
3309
|
tempest db downgrade # roll back one step
|
|
3310
3310
|
tempest db current # print the applied revision
|
|
3311
3311
|
tempest db history -v # revisions newest → oldest, verbose
|
|
3312
|
+
tempest db seed # run src.db.seeds:seed in one session
|
|
3313
|
+
tempest db seed --seed src.db.fixtures:demo # custom seed callable
|
|
3314
|
+
```
|
|
3315
|
+
|
|
3316
|
+
`tempest db seed` runs a project seed callable (default `src.db.seeds:seed`, sync or async, taking one `AsyncSession`) inside a managed session — commit on success, rollback on error.
|
|
3317
|
+
|
|
3318
|
+
#### Secrets — `tempest secrets`
|
|
3319
|
+
|
|
3320
|
+
Generates and rotates application secrets (`JWT_SECRET` / `TOKEN_SECRET` by default), rewriting the matching `.env` lines in place after a `.env.bak` backup; `--print` writes nothing and emits the values to stdout.
|
|
3321
|
+
|
|
3322
|
+
```bash
|
|
3323
|
+
tempest secrets rotate # rotate JWT_SECRET + TOKEN_SECRET in .env
|
|
3324
|
+
tempest secrets rotate --print # just print, write nothing
|
|
3325
|
+
tempest secrets rotate --keys JWT_SECRET,SESSION_SECRET --env .env.prod
|
|
3326
|
+
tempest secrets rotate --length 64 --no-backup
|
|
3312
3327
|
```
|
|
3313
3328
|
|
|
3314
3329
|
#### Users — `tempest user`
|
|
@@ -140,8 +140,29 @@ tempest db downgrade <rev> # roll back to a specific revis
|
|
|
140
140
|
tempest db current # print the applied revision
|
|
141
141
|
tempest db history # revisions newest → oldest
|
|
142
142
|
tempest db history -v # with full message body
|
|
143
|
+
tempest db seed # runs src.db.seeds:seed
|
|
144
|
+
tempest db seed --seed src.db.fixtures:demo # custom callable
|
|
143
145
|
```
|
|
144
146
|
|
|
147
|
+
#### Seed the database — `tempest db seed`
|
|
148
|
+
|
|
149
|
+
Runs a project seed callable inside a managed session (commit on success, rollback on error). The callable takes a positional `AsyncSession` and may be sync or async; what it inserts is up to you — the SDK only wires the session lifecycle. Defaults to importing `src.db.seeds:seed`.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
153
|
+
|
|
154
|
+
from src.db.models import CategoryModel
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
async def seed(session: AsyncSession) -> int:
|
|
158
|
+
"""Seed initial categories. Return the count (optional)."""
|
|
159
|
+
session.add_all([CategoryModel(name="Books"), CategoryModel(name="Games")])
|
|
160
|
+
await session.flush()
|
|
161
|
+
return 2
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
When the callable returns an `int`, the CLI prints the count: `Seeded via src.db.seeds:seed (2 rows).`
|
|
165
|
+
|
|
145
166
|
### Users — `tempest user`
|
|
146
167
|
|
|
147
168
|
Seed and list users using the project's concrete ``UserModel`` (default ``src.db.models:UserModel``). Bootstraps the first admin without manual SQL.
|
|
@@ -180,6 +201,27 @@ tempest user list --admin # admins only
|
|
|
180
201
|
|
|
181
202
|
``DATABASE_URL`` resolves the same way as ``tempest db`` (env var > settings > alembic.ini).
|
|
182
203
|
|
|
204
|
+
### Secrets — `tempest secrets`
|
|
205
|
+
|
|
206
|
+
Generate and rotate application secrets (`JWT_SECRET` / `TOKEN_SECRET` by default), rewriting the matching `.env` lines **in place** — backing up the old file first — and leaving every other line untouched.
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
# Rotate JWT_SECRET and TOKEN_SECRET in .env (writes .env.bak)
|
|
210
|
+
tempest secrets rotate
|
|
211
|
+
|
|
212
|
+
# Just print the new values (writes nothing) — pipe into a secret manager
|
|
213
|
+
tempest secrets rotate --print
|
|
214
|
+
|
|
215
|
+
# Custom keys and file
|
|
216
|
+
tempest secrets rotate --keys JWT_SECRET,SESSION_SECRET --env .env.prod
|
|
217
|
+
|
|
218
|
+
# More entropy, no backup
|
|
219
|
+
tempest secrets rotate --length 64 --no-backup
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
!!! warning
|
|
223
|
+
Rotating `JWT_SECRET` invalidates every token signed with the old value: users are logged out and pending reset/activation links stop working. Rotate during a maintenance window and restart the service to load the new values.
|
|
224
|
+
|
|
183
225
|
### Regenerating `docker-compose.yaml` in an existing project
|
|
184
226
|
|
|
185
227
|
When you change installed extras (`uv add "tempest-fastapi-sdk[minio]"`) or the SDK bumps image versions, regenerate with:
|
|
@@ -188,8 +188,29 @@ tempest db downgrade <rev> # rollback até rev específico
|
|
|
188
188
|
tempest db current # imprime revision aplicado
|
|
189
189
|
tempest db history # histórico de revisions
|
|
190
190
|
tempest db history -v # com message body completo
|
|
191
|
+
tempest db seed # roda src.db.seeds:seed
|
|
192
|
+
tempest db seed --seed src.db.fixtures:demo # callable customizado
|
|
191
193
|
```
|
|
192
194
|
|
|
195
|
+
#### Popular o banco — `tempest db seed`
|
|
196
|
+
|
|
197
|
+
Roda um callable de seed do projeto dentro de uma sessão gerenciada (commit no sucesso, rollback no erro). O callable recebe uma `AsyncSession` posicional e pode ser sync ou async; o que ele insere é decisão sua — o SDK só cuida do ciclo de vida da sessão. Por padrão importa `src.db.seeds:seed`.
|
|
198
|
+
|
|
199
|
+
```python
|
|
200
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
201
|
+
|
|
202
|
+
from src.db.models import CategoryModel
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
async def seed(session: AsyncSession) -> int:
|
|
206
|
+
"""Popula categorias iniciais. Retorna a contagem (opcional)."""
|
|
207
|
+
session.add_all([CategoryModel(name="Livros"), CategoryModel(name="Games")])
|
|
208
|
+
await session.flush()
|
|
209
|
+
return 2
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Quando o callable devolve um `int`, a CLI mostra a contagem: `Seeded via src.db.seeds:seed (2 rows).`
|
|
213
|
+
|
|
193
214
|
### Usuários — `tempest user`
|
|
194
215
|
|
|
195
216
|
Insere/lista usuários direto no banco usando o `UserModel` concreto do projeto (default `src.db.models:UserModel`). Útil pra bootstrapear o primeiro admin sem rodar SQL manual.
|
|
@@ -228,6 +249,27 @@ tempest user list --admin # só admins
|
|
|
228
249
|
|
|
229
250
|
Resolução do `DATABASE_URL` igual ao `tempest db` (env var > settings > alembic.ini).
|
|
230
251
|
|
|
252
|
+
### Segredos — `tempest secrets`
|
|
253
|
+
|
|
254
|
+
Gera e rotaciona os segredos da aplicação (`JWT_SECRET` / `TOKEN_SECRET` por padrão), reescrevendo as linhas correspondentes no `.env` **no lugar** — fazendo backup do arquivo antigo antes — e deixando as outras linhas intactas.
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Rotaciona JWT_SECRET e TOKEN_SECRET no .env (gera .env.bak)
|
|
258
|
+
tempest secrets rotate
|
|
259
|
+
|
|
260
|
+
# Só imprime os novos valores (não escreve nada) — pra pipar num secret manager
|
|
261
|
+
tempest secrets rotate --print
|
|
262
|
+
|
|
263
|
+
# Chaves e arquivo customizados
|
|
264
|
+
tempest secrets rotate --keys JWT_SECRET,SESSION_SECRET --env .env.prod
|
|
265
|
+
|
|
266
|
+
# Mais entropia, sem backup
|
|
267
|
+
tempest secrets rotate --length 64 --no-backup
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
!!! warning
|
|
271
|
+
Rotacionar `JWT_SECRET` invalida todo token assinado com o valor antigo: usuários são deslogados e links de reset/ativação pendentes param de funcionar. Rotacione numa janela de manutenção e reinicie o serviço pra carregar os novos valores.
|
|
272
|
+
|
|
231
273
|
#### Gates de qualidade
|
|
232
274
|
|
|
233
275
|
Os comandos de lint chamam a ferramenta do projeto. Eles procuram o executável no `PATH` primeiro e, caso contrário, caem para `uv run <tool>` para que um virtualenv local do projeto funcione sem ativação manual.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Safe deploys (migrations + graceful shutdown)
|
|
2
|
+
|
|
3
|
+
Two classic deploy risks: a migration that **deletes data** by accident,
|
|
4
|
+
and a rollout that **severs in-flight requests** when the old pod dies.
|
|
5
|
+
This recipe covers the two defenses the SDK ships.
|
|
6
|
+
|
|
7
|
+
## Safe migrations: `safe_upgrade`
|
|
8
|
+
|
|
9
|
+
`AlembicHelper.safe_upgrade()` runs the upgrade **only if** no pending
|
|
10
|
+
migration is destructive. It scans each pending revision's `def upgrade()`
|
|
11
|
+
for data-deleting calls — `op.drop_table`, `op.drop_column`,
|
|
12
|
+
`op.drop_constraint` (and `batch_op` variants) — and, if it finds one,
|
|
13
|
+
raises `DestructiveMigrationError` **without touching the database**.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from tempest_fastapi_sdk import AlembicHelper, DestructiveMigrationError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def deploy_migrations() -> None:
|
|
20
|
+
"""Apply migrations on deploy, blocking accidental DROPs."""
|
|
21
|
+
helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
|
|
22
|
+
try:
|
|
23
|
+
helper.safe_upgrade("head")
|
|
24
|
+
except DestructiveMigrationError as exc:
|
|
25
|
+
# CI/CD fails here — someone must review and unblock with force.
|
|
26
|
+
for revision, op in exc.offences:
|
|
27
|
+
print(f"blocked: {revision} → {op}")
|
|
28
|
+
raise
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The scan looks at the migration **code**, not the generated SQL — so it
|
|
32
|
+
never false-positives on the table rebuild SQLite does in batch mode. A
|
|
33
|
+
`drop_*` in `downgrade()` (the normal, expected path) is ignored.
|
|
34
|
+
|
|
35
|
+
### Allowing an intentional DROP
|
|
36
|
+
|
|
37
|
+
When the DROP is intentional (you took a backup, you reviewed it), pass
|
|
38
|
+
`force=True` — the destructive operations are logged and the upgrade runs:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from tempest_fastapi_sdk import AlembicHelper
|
|
42
|
+
|
|
43
|
+
helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
|
|
44
|
+
helper.safe_upgrade("head", force=True) # I know what I'm doing
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
!!! tip "Inspect only"
|
|
48
|
+
`helper.pending_destructive_ops("head")` returns the list of
|
|
49
|
+
`(revision, operation)` without running anything — handy for a CI step
|
|
50
|
+
that only reports.
|
|
51
|
+
|
|
52
|
+
!!! danger "force=True deletes data"
|
|
53
|
+
`DROP COLUMN` / `DROP TABLE` are irreversible. Only use `force=True`
|
|
54
|
+
after a backup and human review.
|
|
55
|
+
|
|
56
|
+
## Graceful shutdown: drain in-flight requests
|
|
57
|
+
|
|
58
|
+
On a rollout the orchestrator sends `SIGTERM` and, after a grace period,
|
|
59
|
+
`SIGKILL`. If a request is still running when the worker dies, it's
|
|
60
|
+
severed — an intermittent 502. `GracefulShutdownMiddleware`:
|
|
61
|
+
|
|
62
|
+
1. Once **draining**, replies `503` + `Retry-After` to new requests, so
|
|
63
|
+
the load balancer stops routing to this pod.
|
|
64
|
+
2. **Counts** in-flight requests; `wait_drained()` waits for them to
|
|
65
|
+
finish (with a timeout) before the process exits.
|
|
66
|
+
|
|
67
|
+
You hold the instance and drive draining from the `lifespan` (uvicorn runs
|
|
68
|
+
the lifespan shutdown on `SIGTERM` — and it owns the signal handling):
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from contextlib import asynccontextmanager
|
|
72
|
+
from collections.abc import AsyncIterator
|
|
73
|
+
|
|
74
|
+
from fastapi import FastAPI
|
|
75
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
76
|
+
|
|
77
|
+
from tempest_fastapi_sdk import GracefulShutdownMiddleware
|
|
78
|
+
|
|
79
|
+
shutdown: GracefulShutdownMiddleware = GracefulShutdownMiddleware(drain_timeout=25.0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@asynccontextmanager
|
|
83
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
84
|
+
"""Drain in-flight requests on shutdown."""
|
|
85
|
+
yield
|
|
86
|
+
shutdown.begin_drain()
|
|
87
|
+
await shutdown.wait_drained()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
app: FastAPI = FastAPI(lifespan=lifespan)
|
|
91
|
+
app.add_middleware(BaseHTTPMiddleware, dispatch=shutdown.dispatch)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Set the orchestrator's grace period a little **above** `drain_timeout`,
|
|
95
|
+
and uvicorn's `--timeout-graceful-shutdown` to match.
|
|
96
|
+
|
|
97
|
+
!!! warning "The signal belongs to your server"
|
|
98
|
+
uvicorn already installs `SIGTERM` handlers and triggers the lifespan
|
|
99
|
+
shutdown — drive draining from there. The opt-in
|
|
100
|
+
`install_signal_handlers()` is only for servers that do **not** manage
|
|
101
|
+
signals themselves; it chains the previous handler and is a no-op off
|
|
102
|
+
the main thread.
|
|
103
|
+
|
|
104
|
+
## Recap
|
|
105
|
+
|
|
106
|
+
- `AlembicHelper.safe_upgrade()` refuses destructive migrations
|
|
107
|
+
(`DestructiveMigrationError`); `force=True` allows them;
|
|
108
|
+
`pending_destructive_ops()` only inspects.
|
|
109
|
+
- `GracefulShutdownMiddleware` replies `503` while draining and
|
|
110
|
+
`wait_drained()` waits for in-flight requests — driven from the
|
|
111
|
+
`lifespan`.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Deploy seguro (migrações + graceful shutdown)
|
|
2
|
+
|
|
3
|
+
Dois riscos clássicos de deploy: uma migration que **apaga dados** sem
|
|
4
|
+
querer, e um rollout que **corta requests no meio** quando o pod velho
|
|
5
|
+
morre. Esta receita cobre as duas defesas que o SDK traz.
|
|
6
|
+
|
|
7
|
+
## Migrações seguras: `safe_upgrade`
|
|
8
|
+
|
|
9
|
+
`AlembicHelper.safe_upgrade()` roda o upgrade **só se** nenhuma migration
|
|
10
|
+
pendente for destrutiva. Ele varre o `def upgrade()` de cada revision
|
|
11
|
+
pendente atrás de chamadas que apagam dados — `op.drop_table`,
|
|
12
|
+
`op.drop_column`, `op.drop_constraint` (e variantes `batch_op`) — e, se
|
|
13
|
+
achar alguma, levanta `DestructiveMigrationError` **sem tocar no banco**.
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from tempest_fastapi_sdk import AlembicHelper, DestructiveMigrationError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def deploy_migrations() -> None:
|
|
20
|
+
"""Aplica migrations no deploy, barrando DROPs acidentais."""
|
|
21
|
+
helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
|
|
22
|
+
try:
|
|
23
|
+
helper.safe_upgrade("head")
|
|
24
|
+
except DestructiveMigrationError as exc:
|
|
25
|
+
# CI/CD falha aqui — alguém precisa revisar e liberar com force.
|
|
26
|
+
for revision, op in exc.offences:
|
|
27
|
+
print(f"bloqueado: {revision} → {op}")
|
|
28
|
+
raise
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
A varredura olha o **código** da migration, não o SQL gerado — então não
|
|
32
|
+
dá falso-positivo no rebuild de tabela que o SQLite faz em batch mode. Um
|
|
33
|
+
`drop_*` no `downgrade()` (o caminho normal e esperado) é ignorado.
|
|
34
|
+
|
|
35
|
+
### Liberando um DROP intencional
|
|
36
|
+
|
|
37
|
+
Quando o DROP é proposital (você já fez backup, já validou), passe
|
|
38
|
+
`force=True` — as operações destrutivas são logadas e o upgrade roda:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from tempest_fastapi_sdk import AlembicHelper
|
|
42
|
+
|
|
43
|
+
helper: AlembicHelper = AlembicHelper(db_url="postgresql+asyncpg://...")
|
|
44
|
+
helper.safe_upgrade("head", force=True) # eu sei o que estou fazendo
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
!!! tip "Só inspecionar"
|
|
48
|
+
`helper.pending_destructive_ops("head")` devolve a lista de
|
|
49
|
+
`(revision, operação)` sem rodar nada — útil pra um passo de CI que só
|
|
50
|
+
reporta.
|
|
51
|
+
|
|
52
|
+
!!! danger "force=True apaga dados"
|
|
53
|
+
`DROP COLUMN` / `DROP TABLE` são irreversíveis. Só use `force=True`
|
|
54
|
+
depois de backup e revisão humana.
|
|
55
|
+
|
|
56
|
+
## Graceful shutdown: drenar requests em voo
|
|
57
|
+
|
|
58
|
+
No rollout, o orquestrador manda `SIGTERM` e, depois de um tempo,
|
|
59
|
+
`SIGKILL`. Se uma request ainda estiver rodando quando o worker morre, ela
|
|
60
|
+
é cortada — vira um 502 intermitente. `GracefulShutdownMiddleware`:
|
|
61
|
+
|
|
62
|
+
1. Ao entrar em **drenagem**, responde `503` + `Retry-After` pra requests
|
|
63
|
+
novas, então o load balancer para de rotear pra esse pod.
|
|
64
|
+
2. **Conta** as requests em voo; `wait_drained()` espera elas terminarem
|
|
65
|
+
(com timeout) antes do processo sair.
|
|
66
|
+
|
|
67
|
+
Você segura a instância e dirige a drenagem pelo `lifespan` (o uvicorn
|
|
68
|
+
roda o shutdown do lifespan no `SIGTERM` — e é ele quem cuida do sinal):
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from contextlib import asynccontextmanager
|
|
72
|
+
from collections.abc import AsyncIterator
|
|
73
|
+
|
|
74
|
+
from fastapi import FastAPI
|
|
75
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
76
|
+
|
|
77
|
+
from tempest_fastapi_sdk import GracefulShutdownMiddleware
|
|
78
|
+
|
|
79
|
+
shutdown: GracefulShutdownMiddleware = GracefulShutdownMiddleware(drain_timeout=25.0)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@asynccontextmanager
|
|
83
|
+
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
84
|
+
"""Drena as requests em voo no shutdown."""
|
|
85
|
+
yield
|
|
86
|
+
shutdown.begin_drain()
|
|
87
|
+
await shutdown.wait_drained()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
app: FastAPI = FastAPI(lifespan=lifespan)
|
|
91
|
+
app.add_middleware(BaseHTTPMiddleware, dispatch=shutdown.dispatch)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Configure o grace period do orquestrador um pouco **acima** do
|
|
95
|
+
`drain_timeout`, e o `--timeout-graceful-shutdown` do uvicorn pra casar.
|
|
96
|
+
|
|
97
|
+
!!! warning "O sinal é do seu servidor"
|
|
98
|
+
O uvicorn já instala handlers de `SIGTERM` e dispara o shutdown do
|
|
99
|
+
lifespan — dirija a drenagem por lá. O método opt-in
|
|
100
|
+
`install_signal_handlers()` só serve pra servidores que **não**
|
|
101
|
+
gerenciam sinais sozinhos; ele encadeia o handler anterior e é no-op
|
|
102
|
+
fora da thread principal.
|
|
103
|
+
|
|
104
|
+
## Recap
|
|
105
|
+
|
|
106
|
+
- `AlembicHelper.safe_upgrade()` recusa migrations destrutivas
|
|
107
|
+
(`DestructiveMigrationError`); `force=True` libera; `pending_destructive_ops()`
|
|
108
|
+
só inspeciona.
|
|
109
|
+
- `GracefulShutdownMiddleware` responde `503` durante a drenagem e
|
|
110
|
+
`wait_drained()` espera as requests em voo — dirigido pelo `lifespan`.
|
|
@@ -26,7 +26,8 @@ Bite-sized "I want to wire X" walkthroughs. Each page starts with **what problem
|
|
|
26
26
|
| **[Downloads »](downloads.md)** | `DownloadUtils` — `file_response`, `stream`, `build_content_disposition`, path-traversal safe |
|
|
27
27
|
| **[Utilities »](utilities.md)** | `utcnow`/`to_utc`, `modify_dict`, `get_client_ip`, opaque tokens (`generate_opaque_token`) |
|
|
28
28
|
| **[Testing »](testing.md)** | `test_session`, `test_database`, in-memory SQLite, pytest fixtures |
|
|
29
|
-
| **[
|
|
29
|
+
| **[Safe deploys »](deploy-safety.md)** | `AlembicHelper.safe_upgrade` (blocks DROPs), `GracefulShutdownMiddleware` |
|
|
30
|
+
| **[CLI »](cli.md)** | `tempest new` / `db` (+ `seed`) / `user` / `secrets rotate` / `lint` / `fix` / `format` / `type` / `test` / `check` |
|
|
30
31
|
| **[Security »](security.md)** | `AttemptThrottle`, opaque-token helpers, `HardenedStaticFiles`, security headers |
|
|
31
32
|
| **[Brazilian helpers »](br-helpers.md)** | CPF / CNPJ / CEP / phone validation + normalization |
|
|
32
33
|
|
|
@@ -26,7 +26,8 @@ Passo a passo curtos no estilo "quero conectar X". Cada página começa com **qu
|
|
|
26
26
|
| **[Downloads »](downloads.md)** | `DownloadUtils` — `file_response`, `stream`, `build_content_disposition`, anti path-traversal |
|
|
27
27
|
| **[Utilitários »](utilities.md)** | `utcnow`/`to_utc`, `modify_dict`, `get_client_ip`, tokens opacos (`generate_opaque_token`) |
|
|
28
28
|
| **[Testes »](testing.md)** | `test_session`, `test_database`, SQLite em memória, fixtures pytest |
|
|
29
|
-
| **[
|
|
29
|
+
| **[Deploy seguro »](deploy-safety.md)** | `AlembicHelper.safe_upgrade` (barra DROPs), `GracefulShutdownMiddleware` |
|
|
30
|
+
| **[CLI »](cli.md)** | `tempest new` / `db` (+ `seed`) / `user` / `secrets rotate` / `lint` / `fix` / `format` / `type` / `test` / `check` |
|
|
30
31
|
| **[Segurança »](security.md)** | `AttemptThrottle`, helpers de token opaco, `HardenedStaticFiles`, headers de segurança |
|
|
31
32
|
| **[Helpers brasileiros »](br-helpers.md)** | validação + normalização de CPF / CNPJ / CEP / telefone |
|
|
32
33
|
|
|
@@ -41,6 +41,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
|
|
|
41
41
|
::: tempest_fastapi_sdk.db.outbox.BaseOutboxModel
|
|
42
42
|
::: tempest_fastapi_sdk.db.outbox.OutboxRelay
|
|
43
43
|
::: tempest_fastapi_sdk.db.outbox.OutboxStatus
|
|
44
|
+
::: tempest_fastapi_sdk.db.migrations.DestructiveMigrationError
|
|
44
45
|
|
|
45
46
|
---
|
|
46
47
|
|
|
@@ -96,6 +97,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
|
|
|
96
97
|
::: tempest_fastapi_sdk.api.middlewares.idempotency.MemoryIdempotencyStore
|
|
97
98
|
::: tempest_fastapi_sdk.api.middlewares.idempotency.RedisIdempotencyStore
|
|
98
99
|
::: tempest_fastapi_sdk.api.middlewares.body_size.BodySizeLimitMiddleware
|
|
100
|
+
::: tempest_fastapi_sdk.api.middlewares.graceful.GracefulShutdownMiddleware
|
|
99
101
|
::: tempest_fastapi_sdk.api.middlewares.csrf.CSRFMiddleware
|
|
100
102
|
::: tempest_fastapi_sdk.api.middlewares.csrf.make_csrf_token_dependency
|
|
101
103
|
::: tempest_fastapi_sdk.api.middlewares.csrf.generate_csrf_token
|
|
@@ -107,6 +107,7 @@ plugins:
|
|
|
107
107
|
Métricas: Metrics
|
|
108
108
|
Painel admin: Admin site
|
|
109
109
|
Testes: Testing
|
|
110
|
+
"Deploy seguro (migrations + shutdown)": "Safe deploys (migrations + shutdown)"
|
|
110
111
|
CLI: CLI
|
|
111
112
|
Segurança: Security
|
|
112
113
|
Utilitários: Utilities
|
|
@@ -223,6 +224,7 @@ nav:
|
|
|
223
224
|
- Métricas: recipes/metrics.md
|
|
224
225
|
- Painel admin: recipes/admin.md
|
|
225
226
|
- Testes: recipes/testing.md
|
|
227
|
+
- "Deploy seguro (migrations + shutdown)": recipes/deploy-safety.md
|
|
226
228
|
- CLI: recipes/cli.md
|
|
227
229
|
- Segurança: recipes/security.md
|
|
228
230
|
- Utilitários: recipes/utilities.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tempest-fastapi-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.47.0"
|
|
4
4
|
description = "Shared FastAPI building blocks: base schemas, ORM model, async repository, exceptions, pagination and settings — the conventions used across Tempest projects."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -21,6 +21,7 @@ from tempest_fastapi_sdk.api import (
|
|
|
21
21
|
CSRFMiddleware,
|
|
22
22
|
GitHubOAuthClient,
|
|
23
23
|
GoogleOAuthClient,
|
|
24
|
+
GracefulShutdownMiddleware,
|
|
24
25
|
HardenedStaticFiles,
|
|
25
26
|
HealthCheck,
|
|
26
27
|
IdempotencyMiddleware,
|
|
@@ -104,6 +105,7 @@ from tempest_fastapi_sdk.db import (
|
|
|
104
105
|
BaseUserModel,
|
|
105
106
|
BaseUserRecoveryCodeModel,
|
|
106
107
|
BaseUserTokenModel,
|
|
108
|
+
DestructiveMigrationError,
|
|
107
109
|
MFAMixin,
|
|
108
110
|
OutboxRelay,
|
|
109
111
|
OutboxStatus,
|
|
@@ -248,7 +250,7 @@ from tempest_fastapi_sdk.websockets import (
|
|
|
248
250
|
make_websocket_router,
|
|
249
251
|
)
|
|
250
252
|
|
|
251
|
-
__version__: str = "0.
|
|
253
|
+
__version__: str = "0.47.0"
|
|
252
254
|
|
|
253
255
|
__all__: list[str] = [
|
|
254
256
|
"BASE_COLUMN_ORDER",
|
|
@@ -306,6 +308,7 @@ __all__: list[str] = [
|
|
|
306
308
|
"CursorPaginationFilterSchema",
|
|
307
309
|
"CursorPaginationSchema",
|
|
308
310
|
"DatabaseSettings",
|
|
311
|
+
"DestructiveMigrationError",
|
|
309
312
|
"DiskMetrics",
|
|
310
313
|
"DownloadUtils",
|
|
311
314
|
"EmailSettings",
|
|
@@ -318,6 +321,7 @@ __all__: list[str] = [
|
|
|
318
321
|
"GPUMetrics",
|
|
319
322
|
"GitHubOAuthClient",
|
|
320
323
|
"GoogleOAuthClient",
|
|
324
|
+
"GracefulShutdownMiddleware",
|
|
321
325
|
"HTTPClient",
|
|
322
326
|
"HardenedStaticFiles",
|
|
323
327
|
"HealthCheck",
|
{tempest_fastapi_sdk-0.45.0 → tempest_fastapi_sdk-0.47.0}/tempest_fastapi_sdk/api/__init__.py
RENAMED
|
@@ -27,6 +27,7 @@ from tempest_fastapi_sdk.api.middlewares import (
|
|
|
27
27
|
BodySizeLimitMiddleware,
|
|
28
28
|
CachedResponse,
|
|
29
29
|
CSRFMiddleware,
|
|
30
|
+
GracefulShutdownMiddleware,
|
|
30
31
|
IdempotencyMiddleware,
|
|
31
32
|
IdempotencyStore,
|
|
32
33
|
MemoryIdempotencyStore,
|
|
@@ -79,6 +80,7 @@ __all__: list[str] = [
|
|
|
79
80
|
"CachedResponse",
|
|
80
81
|
"GitHubOAuthClient",
|
|
81
82
|
"GoogleOAuthClient",
|
|
83
|
+
"GracefulShutdownMiddleware",
|
|
82
84
|
"HardenedStaticFiles",
|
|
83
85
|
"HealthCheck",
|
|
84
86
|
"IdempotencyMiddleware",
|
|
@@ -9,6 +9,7 @@ from tempest_fastapi_sdk.api.middlewares.csrf import (
|
|
|
9
9
|
generate_csrf_token,
|
|
10
10
|
make_csrf_token_dependency,
|
|
11
11
|
)
|
|
12
|
+
from tempest_fastapi_sdk.api.middlewares.graceful import GracefulShutdownMiddleware
|
|
12
13
|
from tempest_fastapi_sdk.api.middlewares.idempotency import (
|
|
13
14
|
IDEMPOTENCY_HEADER,
|
|
14
15
|
CachedResponse,
|
|
@@ -27,6 +28,7 @@ __all__: list[str] = [
|
|
|
27
28
|
"BodySizeLimitMiddleware",
|
|
28
29
|
"CSRFMiddleware",
|
|
29
30
|
"CachedResponse",
|
|
31
|
+
"GracefulShutdownMiddleware",
|
|
30
32
|
"IdempotencyMiddleware",
|
|
31
33
|
"IdempotencyStore",
|
|
32
34
|
"MemoryIdempotencyStore",
|