tempest-fastapi-sdk 0.90.0__tar.gz → 0.91.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.90.0 → tempest_fastapi_sdk-0.91.0}/CHANGELOG.md +46 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/CLAUDE.md +11 -4
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/PKG-INFO +9 -6
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/README.md +8 -5
- tempest_fastapi_sdk-0.91.0/docs/recipes/sse.en.md +390 -0
- tempest_fastapi_sdk-0.91.0/docs/recipes/sse.md +390 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/pyproject.toml +1 -1
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/__init__.py +3 -1
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/dependencies/auth.py +38 -7
- tempest_fastapi_sdk-0.91.0/tempest_fastapi_sdk/sse/__init__.py +23 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sse/broker.py +53 -2
- tempest_fastapi_sdk-0.91.0/tempest_fastapi_sdk/sse/event_stream.py +368 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_jwt_dependency.py +75 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/sse/test_broker.py +35 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/sse/test_event_stream.py +98 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/uv.lock +1 -1
- tempest_fastapi_sdk-0.90.0/docs/recipes/sse.en.md +0 -226
- tempest_fastapi_sdk-0.90.0/docs/recipes/sse.md +0 -226
- tempest_fastapi_sdk-0.90.0/tempest_fastapi_sdk/sse/__init__.py +0 -15
- tempest_fastapi_sdk-0.90.0/tempest_fastapi_sdk/sse/event_stream.py +0 -205
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/.github/workflows/ci.yml +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/.github/workflows/docs.yml +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/.gitignore +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/.python-version +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/Makefile +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/architecture.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/architecture.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/changelog.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/changelog.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/contributing.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/contributing.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/index.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/index.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/installation.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/installation.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/index.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/index.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/api.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/api.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/business-rules.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/business-rules.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/domain.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/domain.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/flows.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/flows.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/index.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/learning/marketplace/index.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/migration.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/migration.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/admin.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/admin.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/audit-trail.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/audit-trail.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/auth-flow.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/auth-flow.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/br-helpers.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/br-helpers.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/cache.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/cache.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/cli.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/cli.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/database.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/database.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/deploy-safety.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/deploy-safety.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/downloads.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/downloads.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/email.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/email.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/feature-flags.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/feature-flags.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/fields.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/fields.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/file-store.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/file-store.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/http-client.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/http-client.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/http.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/http.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/idempotency.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/idempotency.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/index.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/index.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/logging.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/logging.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/metrics.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/metrics.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/mfa.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/mfa.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/multi-tenant.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/multi-tenant.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/observability.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/observability.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/offline-sync.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/offline-sync.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/outbox.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/outbox.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/queue-tasks.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/queue-tasks.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/realtime.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/realtime.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/refresh-tokens.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/refresh-tokens.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/security.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/security.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/sessions.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/sessions.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/storage.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/storage.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/stored-files.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/stored-files.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/testing.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/testing.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/typing.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/typing.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/uploads.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/uploads.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/utilities.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/utilities.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/vision.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/vision.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/webpush.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/webpush.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/websocket.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/recipes/websocket.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/reference.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/reference.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/roadmap.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/roadmap.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/ssr.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/ssr.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/tutorial.en.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/docs/tutorial.md +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/mkdocs.yml +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/mkdocs_hooks/llmstxt.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/scripts/extract_recipe.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/actions.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/config.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/discovery.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/session.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/site.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/admin/theme.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/server.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/static.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/guards.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/locale.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/service.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/activation.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/auth/token_delivery.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cache/invalidation.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/Dockerfile.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/dockerignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/config.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/db.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/main.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/new.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/cli/user.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/core/context.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/core/enums.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/core/logging.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/core/typed.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/audit.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/backup.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/connection.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/repository.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/user_refresh_token_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/db/webpush_subscription_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/i18n.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/flags/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/flags/backends.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/flags/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/flags/service.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/py.typed +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/services/base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/services/file_mixin.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/settings/base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/ssr/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/ssr/_static/htmx.min.js +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/ssr/assets.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/ssr/page.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/ssr/response.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/testing/database.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/data/br_locations.json +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/download.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/email.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/fields.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/file_store.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/locations.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/log.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/password.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/vision/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/vision/mapping.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/vision/schemas.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/webpush/router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/webpush/service.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_actions.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_auth.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_discovery.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_forms.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_logs_nav.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_rich_filters.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_site.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_theme.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_upload_field.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/admin/test_user_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_body_size.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_cookies.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_cors.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_csrf.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_dependencies_auth.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_graceful.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_handlers.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_health_router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_idempotency.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_logs_router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_oauth.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_prometheus.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_rate_limit_extras.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_request_id_middleware.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_role_dependency.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_server.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_static.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_tracing.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_webhooks.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/api/test_webhooks_rsa.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_cookie_delivery.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_guards.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_locale.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_refresh.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_refresh_db.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/auth/test_service.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cache/test_decorator.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cache/test_invalidation.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cache/test_redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_config.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_db.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_db_seed.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_generate.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_lint_strictness.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_main.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_secrets.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/cli/test_user.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/conftest.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/controllers/test_base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/core/test_context.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/core/test_enums.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/core/test_logging.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/core/test_typed.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_audit.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_backup.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_bulk_ops.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_connection.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_migrations.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_model.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_outbox.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_repository.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_safe_upgrade.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_slow_query.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/db/test_tenant.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/exceptions/test_exceptions.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/exceptions/test_i18n.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/flags/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/flags/test_flags.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/queue/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/schemas/test_base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/schemas/test_cursor_pagination.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/schemas/test_link_headers.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/schemas/test_pagination.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/schemas/test_response.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/services/test_base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/services/test_file_mixin.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/sessions/test_sessions.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/settings/test_base.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/settings/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/ssr/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/ssr/test_integration.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/ssr/test_page.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/ssr/test_response.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/storage/test_minio_client.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/tasks/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/tasks/test_scheduler.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/testing/test_database.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_client_ip.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_datetime.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_dict.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_download.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_email.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_fields.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_file_store.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_http_client.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_jwt.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_lazy_extras.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_locations.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_log.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_metrics.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_password.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_regex.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_throttle.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/utils/test_upload.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/vision/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/vision/test_vision.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/webpush/test_dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/webpush/test_router.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/webpush/test_schemas.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/webpush/test_service.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.90.0 → tempest_fastapi_sdk-0.91.0}/tests/websockets/test_hub_and_router.py +0 -0
|
@@ -5,6 +5,52 @@ 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.91.0] — 2026-07-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **SSE backpressure — bounded queue + overflow policy.**
|
|
13
|
+
`EventStream` (and `SSEBroker`-created streams) now cap the buffered
|
|
14
|
+
events at `max_queue` (default `1000`) instead of growing without
|
|
15
|
+
limit when a client stalls. When the buffer fills, `overflow` decides
|
|
16
|
+
what gives:
|
|
17
|
+
- `"drop_oldest"` (default) — evict the stalest event, keep the
|
|
18
|
+
freshest data.
|
|
19
|
+
- `"drop_newest"` — discard the incoming event, keep the backlog.
|
|
20
|
+
- `"block"` — apply real backpressure (the producer waits for a slot).
|
|
21
|
+
`EventStream.dropped_events` counts events lost to overflow for
|
|
22
|
+
metrics / logs. `max_queue=0` restores the pre-0.91 unbounded
|
|
23
|
+
behavior. The `close()` sentinel is never dropped or blocked, so a
|
|
24
|
+
stream can always terminate. `SSEBroker(max_queue=..., overflow=...)`
|
|
25
|
+
applies the same policy to every stream it opens.
|
|
26
|
+
- **SSE lifecycle helpers — no more hand-rolled `try/finally`.**
|
|
27
|
+
- `sse_response(..., on_disconnect=...)` runs a cleanup callback
|
|
28
|
+
(awaited if a coroutine) when the client disconnects or the stream
|
|
29
|
+
ends — the one place guaranteed to fire — so a bound producer task
|
|
30
|
+
is cancelled or a channel unregistered without boilerplate.
|
|
31
|
+
- `EventStream.response(*, on_disconnect=..., status_code=...,
|
|
32
|
+
headers=...)` wraps `stream()` in an SSE response in one call.
|
|
33
|
+
- `SSEBroker.response(channel, ...)` bundles the whole per-connection
|
|
34
|
+
lifecycle: `register` + `sse_response` + `unregister`-on-disconnect.
|
|
35
|
+
This removes the leak-prone manual wrapper the recipe used to need.
|
|
36
|
+
- **JWT via query string for cookieless clients.**
|
|
37
|
+
`make_bearer_token_dependency` and `make_jwt_user_dependency` gained a
|
|
38
|
+
`query_param` argument (e.g. `query_param="access_token"`). Token
|
|
39
|
+
lookup order becomes header → cookie → query string. This unblocks
|
|
40
|
+
browser `EventSource` (SSE), whose constructor accepts neither headers
|
|
41
|
+
nor a body. Documented with a security warning: use short-lived access
|
|
42
|
+
tokens only, over TLS, and scrub the value from access logs. Prefer a
|
|
43
|
+
session cookie (`withCredentials`) whenever the client shares the
|
|
44
|
+
API's origin.
|
|
45
|
+
- **`OverflowPolicy`** is exported from `tempest_fastapi_sdk.sse` and the
|
|
46
|
+
package root.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- `tempest_fastapi_sdk/sse/__init__.py` re-exports now use the explicit
|
|
51
|
+
`X as X` alias form required by the repo's re-export convention (was a
|
|
52
|
+
structural defect flagged by strict type-checkers).
|
|
53
|
+
|
|
8
54
|
## [0.90.0] — 2026-07-04
|
|
9
55
|
|
|
10
56
|
### Added
|
|
@@ -55,7 +55,9 @@ without the docs being green.
|
|
|
55
55
|
|
|
56
56
|
The SDK currently covers (Sep 2025+, post-v0.31.x):
|
|
57
57
|
|
|
58
|
-
- **Auth** — JWT/bearer/role/permission/X-Token deps
|
|
58
|
+
- **Auth** — JWT/bearer/role/permission/X-Token deps (JWT deps
|
|
59
|
+
read the token from header → cookie → query string via
|
|
60
|
+
`query_param=`, for cookieless `EventSource`/SSE clients), full
|
|
59
61
|
bundled flow (`UserAuthService` + `make_auth_router` covering
|
|
60
62
|
signup/activate/login/password-reset), `BaseUserModel` +
|
|
61
63
|
`BaseUserTokenModel`, OAuth2/OIDC providers
|
|
@@ -88,9 +90,14 @@ The SDK currently covers (Sep 2025+, post-v0.31.x):
|
|
|
88
90
|
- **Pagination** — offset + cursor.
|
|
89
91
|
- **Settings mixins** — every `*Settings` carries
|
|
90
92
|
`title`/`description`/`examples` on every field.
|
|
91
|
-
- **SSE** — `EventStream
|
|
92
|
-
`
|
|
93
|
-
|
|
93
|
+
- **SSE** — `EventStream` (bounded queue + `overflow` backpressure —
|
|
94
|
+
`drop_oldest`/`drop_newest`/`block`, `dropped_events` counter,
|
|
95
|
+
`max_queue=0` to disable), `ServerSentEvent`, `sse_response`
|
|
96
|
+
(`on_disconnect=` cleanup), `EventStream.response`, and `SSEBroker`
|
|
97
|
+
(per-channel fan-out; `SSEBroker.response(channel)` bundles
|
|
98
|
+
register + response + unregister-on-disconnect; in-memory
|
|
99
|
+
single-process, or multi-worker via an injected Redis pub/sub bridge
|
|
100
|
+
— same call site).
|
|
94
101
|
- **Throttle** — `AttemptThrottle` (any `ThrottleBackend`, e.g.
|
|
95
102
|
`redis.asyncio.Redis`; no in-memory backend bundled).
|
|
96
103
|
- **Base CRUD layers** — `BaseService[Repo, Resp, UpdateT]` and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempest-fastapi-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.91.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
|
|
@@ -200,7 +200,7 @@ Via `pyproject.toml`:
|
|
|
200
200
|
|
|
201
201
|
```toml
|
|
202
202
|
dependencies = [
|
|
203
|
-
"tempest-fastapi-sdk>=0.
|
|
203
|
+
"tempest-fastapi-sdk>=0.91.0",
|
|
204
204
|
]
|
|
205
205
|
```
|
|
206
206
|
|
|
@@ -258,7 +258,7 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
|
|
|
258
258
|
| `tempest_fastapi_sdk.services` | `BaseService` |
|
|
259
259
|
| `tempest_fastapi_sdk.core` | `configure_logging`, `JSONFormatter`, `get_request_id`/`set_request_id`/`clear_request_id`, `request_id_ctx`, `BaseStrEnum`, `BaseIntEnum`, `strict_types`/`typed`/`require_annotations` |
|
|
260
260
|
| `tempest_fastapi_sdk.admin` *(extra: `[admin]`)* | `AdminSite`, `AdminModel`, `make_admin_router`, `AdminAuthBackend`, `UserModelAuthBackend`, `AdminAuthError` |
|
|
261
|
-
| `tempest_fastapi_sdk.sse` | `EventStream
|
|
261
|
+
| `tempest_fastapi_sdk.sse` | `EventStream` (bounded queue + `overflow` backpressure), `SSEBroker` (multi-worker fan-out via Redis, `.response()` lifecycle helper), `ServerSentEvent`, `sse_response` (`on_disconnect=` cleanup) |
|
|
262
262
|
| `tempest_fastapi_sdk.cache` *(extra: `[cache]`)* | `AsyncRedisManager`, `cached` (with `namespace` / `tags`), `CacheInvalidator`, `namespace_registry_key`, `tag_registry_key` |
|
|
263
263
|
| `tempest_fastapi_sdk.flags` | `FeatureFlags`, `FeatureFlagBackend`, `MemoryFeatureFlagBackend`, `EnvFeatureFlagBackend`, `RedisFeatureFlagBackend`, `CompositeFeatureFlagBackend`, `make_flag_dependency`, `coerce_flag` |
|
|
264
264
|
| `tempest_fastapi_sdk.webpush` *(extra: `[webpush]`)* | `WebPushDispatcher`, `WebPushSubscriptionService`, `make_web_push_router`, `WebPushError`, `WebPushGoneError`, `WebPushSubscriptionSchema`, `WebPushKeysSchema`, `WebPushPayloadSchema` |
|
|
@@ -2445,7 +2445,7 @@ import asyncio
|
|
|
2445
2445
|
|
|
2446
2446
|
from fastapi import APIRouter
|
|
2447
2447
|
|
|
2448
|
-
from tempest_fastapi_sdk import EventStream
|
|
2448
|
+
from tempest_fastapi_sdk import EventStream
|
|
2449
2449
|
|
|
2450
2450
|
router = APIRouter()
|
|
2451
2451
|
|
|
@@ -2460,8 +2460,9 @@ async def events() -> "StreamingResponse": # forward-declared by Starlette
|
|
|
2460
2460
|
await asyncio.sleep(1)
|
|
2461
2461
|
await stream.close()
|
|
2462
2462
|
|
|
2463
|
-
asyncio.create_task(producer())
|
|
2464
|
-
|
|
2463
|
+
task = asyncio.create_task(producer())
|
|
2464
|
+
# on_disconnect cancels the producer when the client drops — no leak.
|
|
2465
|
+
return stream.response(on_disconnect=task.cancel)
|
|
2465
2466
|
```
|
|
2466
2467
|
|
|
2467
2468
|
Browser side:
|
|
@@ -2473,6 +2474,8 @@ es.addEventListener("counter", (e) => console.log("got", JSON.parse(e.data)));
|
|
|
2473
2474
|
|
|
2474
2475
|
`heartbeat_seconds` emits a `: keepalive` SSE comment when idle so load-balancers don't close long-lived connections. `ServerSentEvent.data` accepts strings, bytes or any JSON-serializable Python object — non-strings are JSON-encoded automatically. Pass `retry=` to hint the browser at the reconnect delay (milliseconds).
|
|
2475
2476
|
|
|
2477
|
+
The queue is **bounded** (`max_queue`, default `1000`): a slow client can't grow memory without limit. `overflow` picks the eviction policy — `"drop_oldest"` (default), `"drop_newest"`, or `"block"` (real backpressure); `EventStream.dropped_events` counts the discards. For broadcast, `SSEBroker.response(channel)` bundles `register` + `sse_response` + `unregister`-on-disconnect in one call. Authenticating an `EventSource` (which can't send an `Authorization` header): prefer a session cookie (`make_jwt_user_dependency(..., cookie_name="access_token")` + `withCredentials`); for cookieless clients pass a short-lived access token in the query string with `query_param="access_token"` (over TLS, scrubbed from logs). See the [SSE recipe](https://mauriciobenjamin700.github.io/tempest-fastapi-sdk/recipes/sse/) for the full guide.
|
|
2478
|
+
|
|
2476
2479
|
### Web Push notifications recipe
|
|
2477
2480
|
|
|
2478
2481
|
`WebPushDispatcher` wraps the synchronous `pywebpush` library in `asyncio.to_thread` and surfaces the two errors the application cares about: `WebPushGoneError` (HTTP 404/410 — delete the subscription) and `WebPushError` (everything else). Install with `[webpush]`.
|
|
@@ -91,7 +91,7 @@ Via `pyproject.toml`:
|
|
|
91
91
|
|
|
92
92
|
```toml
|
|
93
93
|
dependencies = [
|
|
94
|
-
"tempest-fastapi-sdk>=0.
|
|
94
|
+
"tempest-fastapi-sdk>=0.91.0",
|
|
95
95
|
]
|
|
96
96
|
```
|
|
97
97
|
|
|
@@ -149,7 +149,7 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
|
|
|
149
149
|
| `tempest_fastapi_sdk.services` | `BaseService` |
|
|
150
150
|
| `tempest_fastapi_sdk.core` | `configure_logging`, `JSONFormatter`, `get_request_id`/`set_request_id`/`clear_request_id`, `request_id_ctx`, `BaseStrEnum`, `BaseIntEnum`, `strict_types`/`typed`/`require_annotations` |
|
|
151
151
|
| `tempest_fastapi_sdk.admin` *(extra: `[admin]`)* | `AdminSite`, `AdminModel`, `make_admin_router`, `AdminAuthBackend`, `UserModelAuthBackend`, `AdminAuthError` |
|
|
152
|
-
| `tempest_fastapi_sdk.sse` | `EventStream
|
|
152
|
+
| `tempest_fastapi_sdk.sse` | `EventStream` (bounded queue + `overflow` backpressure), `SSEBroker` (multi-worker fan-out via Redis, `.response()` lifecycle helper), `ServerSentEvent`, `sse_response` (`on_disconnect=` cleanup) |
|
|
153
153
|
| `tempest_fastapi_sdk.cache` *(extra: `[cache]`)* | `AsyncRedisManager`, `cached` (with `namespace` / `tags`), `CacheInvalidator`, `namespace_registry_key`, `tag_registry_key` |
|
|
154
154
|
| `tempest_fastapi_sdk.flags` | `FeatureFlags`, `FeatureFlagBackend`, `MemoryFeatureFlagBackend`, `EnvFeatureFlagBackend`, `RedisFeatureFlagBackend`, `CompositeFeatureFlagBackend`, `make_flag_dependency`, `coerce_flag` |
|
|
155
155
|
| `tempest_fastapi_sdk.webpush` *(extra: `[webpush]`)* | `WebPushDispatcher`, `WebPushSubscriptionService`, `make_web_push_router`, `WebPushError`, `WebPushGoneError`, `WebPushSubscriptionSchema`, `WebPushKeysSchema`, `WebPushPayloadSchema` |
|
|
@@ -2336,7 +2336,7 @@ import asyncio
|
|
|
2336
2336
|
|
|
2337
2337
|
from fastapi import APIRouter
|
|
2338
2338
|
|
|
2339
|
-
from tempest_fastapi_sdk import EventStream
|
|
2339
|
+
from tempest_fastapi_sdk import EventStream
|
|
2340
2340
|
|
|
2341
2341
|
router = APIRouter()
|
|
2342
2342
|
|
|
@@ -2351,8 +2351,9 @@ async def events() -> "StreamingResponse": # forward-declared by Starlette
|
|
|
2351
2351
|
await asyncio.sleep(1)
|
|
2352
2352
|
await stream.close()
|
|
2353
2353
|
|
|
2354
|
-
asyncio.create_task(producer())
|
|
2355
|
-
|
|
2354
|
+
task = asyncio.create_task(producer())
|
|
2355
|
+
# on_disconnect cancels the producer when the client drops — no leak.
|
|
2356
|
+
return stream.response(on_disconnect=task.cancel)
|
|
2356
2357
|
```
|
|
2357
2358
|
|
|
2358
2359
|
Browser side:
|
|
@@ -2364,6 +2365,8 @@ es.addEventListener("counter", (e) => console.log("got", JSON.parse(e.data)));
|
|
|
2364
2365
|
|
|
2365
2366
|
`heartbeat_seconds` emits a `: keepalive` SSE comment when idle so load-balancers don't close long-lived connections. `ServerSentEvent.data` accepts strings, bytes or any JSON-serializable Python object — non-strings are JSON-encoded automatically. Pass `retry=` to hint the browser at the reconnect delay (milliseconds).
|
|
2366
2367
|
|
|
2368
|
+
The queue is **bounded** (`max_queue`, default `1000`): a slow client can't grow memory without limit. `overflow` picks the eviction policy — `"drop_oldest"` (default), `"drop_newest"`, or `"block"` (real backpressure); `EventStream.dropped_events` counts the discards. For broadcast, `SSEBroker.response(channel)` bundles `register` + `sse_response` + `unregister`-on-disconnect in one call. Authenticating an `EventSource` (which can't send an `Authorization` header): prefer a session cookie (`make_jwt_user_dependency(..., cookie_name="access_token")` + `withCredentials`); for cookieless clients pass a short-lived access token in the query string with `query_param="access_token"` (over TLS, scrubbed from logs). See the [SSE recipe](https://mauriciobenjamin700.github.io/tempest-fastapi-sdk/recipes/sse/) for the full guide.
|
|
2369
|
+
|
|
2367
2370
|
### Web Push notifications recipe
|
|
2368
2371
|
|
|
2369
2372
|
`WebPushDispatcher` wraps the synchronous `pywebpush` library in `asyncio.to_thread` and surfaces the two errors the application cares about: `WebPushGoneError` (HTTP 404/410 — delete the subscription) and `WebPushError` (everything else). Install with `[webpush]`.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# Server-Sent Events (SSE)
|
|
2
|
+
|
|
3
|
+
SSE pushes data from the server to the browser over **one long-lived HTTP
|
|
4
|
+
connection**, no polling. It's the simplest path to "one-way real time":
|
|
5
|
+
a notification feed, a progress bar, a price ticker, live logs.
|
|
6
|
+
|
|
7
|
+
!!! info "SSE vs WebSocket vs Web Push"
|
|
8
|
+
- **SSE** — server → client, text only, auto-reconnects, runs over
|
|
9
|
+
plain HTTP. Use when the client only **receives**.
|
|
10
|
+
- **WebSocket** — bidirectional, binary, more complex. Use when the
|
|
11
|
+
client also **sends** often. See [WebSocket](websocket.md).
|
|
12
|
+
- **Web Push** — arrives with the **page closed** (Service Worker).
|
|
13
|
+
See [Web Push](webpush.md).
|
|
14
|
+
|
|
15
|
+
The SDK ships three pieces: `EventStream` (an in-memory async queue
|
|
16
|
+
feeding one connection), `ServerSentEvent` (encodes a frame in the spec
|
|
17
|
+
wire format) and `sse_response` (wraps the stream in a
|
|
18
|
+
`StreamingResponse` with the right headers — `Cache-Control: no-cache`,
|
|
19
|
+
`Connection: keep-alive`, `X-Accel-Buffering: no` to disable nginx
|
|
20
|
+
buffering).
|
|
21
|
+
|
|
22
|
+
!!! tip "New in v0.91"
|
|
23
|
+
- **Backpressure** — the `EventStream` queue is now **bounded**
|
|
24
|
+
(`max_queue`, default `1000`): a slow client can't grow memory
|
|
25
|
+
without limit. The `overflow` policy decides what gives. See
|
|
26
|
+
[Backpressure](#backpressure-bounded-queue).
|
|
27
|
+
- **Lifecycle without boilerplate** — `sse_response(..., on_disconnect=)`,
|
|
28
|
+
`EventStream.response(...)` and `SSEBroker.response(channel)` tear
|
|
29
|
+
down the producer / unregister the channel on their own when the
|
|
30
|
+
client drops.
|
|
31
|
+
- **Query-string auth** for cookieless clients (`EventSource`). See
|
|
32
|
+
[Authentication](#authentication-cookie-or-query-string).
|
|
33
|
+
|
|
34
|
+
## One SSE endpoint
|
|
35
|
+
|
|
36
|
+
Create an `EventStream` per request, publish from a producer, and tie the
|
|
37
|
+
producer's lifecycle to the client connection — if the client drops, the
|
|
38
|
+
producer stops.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
# src/api/routers/events.py
|
|
42
|
+
import asyncio
|
|
43
|
+
|
|
44
|
+
from fastapi import APIRouter
|
|
45
|
+
from starlette.responses import StreamingResponse
|
|
46
|
+
|
|
47
|
+
from tempest_fastapi_sdk import EventStream
|
|
48
|
+
|
|
49
|
+
router = APIRouter()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.get("/events")
|
|
53
|
+
async def events() -> StreamingResponse:
|
|
54
|
+
"""Emit 3 SSE frames then close the stream."""
|
|
55
|
+
stream = EventStream(heartbeat_seconds=15.0)
|
|
56
|
+
|
|
57
|
+
async def producer() -> None:
|
|
58
|
+
try:
|
|
59
|
+
for n in range(1, 4):
|
|
60
|
+
await stream.publish({"n": n}, event="counter", id=str(n))
|
|
61
|
+
await asyncio.sleep(1)
|
|
62
|
+
finally:
|
|
63
|
+
await stream.close()
|
|
64
|
+
|
|
65
|
+
task = asyncio.create_task(producer())
|
|
66
|
+
|
|
67
|
+
# on_disconnect runs when the client drops OR the stream ends:
|
|
68
|
+
# that's where you cancel the producer so it doesn't leak.
|
|
69
|
+
return stream.response(on_disconnect=task.cancel)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
!!! warning "Always tie the producer to the connection"
|
|
73
|
+
SSE streams are long-lived. If the client disconnects mid-stream you
|
|
74
|
+
don't want the producer running forever. Pass `on_disconnect=` to
|
|
75
|
+
`EventStream.response` (or `sse_response`) — it runs in the response
|
|
76
|
+
generator's `finally`, the one place that fires on disconnect.
|
|
77
|
+
|
|
78
|
+
??? note "Before v0.91: hand-rolled `try/finally`"
|
|
79
|
+
Up to v0.90 you wrapped `stream()` in an outer generator just to get
|
|
80
|
+
the `finally`. `on_disconnect=` replaces that boilerplate:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from collections.abc import AsyncIterator
|
|
84
|
+
from tempest_fastapi_sdk import sse_response
|
|
85
|
+
|
|
86
|
+
async def lifecycle_aware() -> AsyncIterator[bytes]:
|
|
87
|
+
try:
|
|
88
|
+
async for chunk in stream.stream():
|
|
89
|
+
yield chunk
|
|
90
|
+
finally:
|
|
91
|
+
task.cancel()
|
|
92
|
+
|
|
93
|
+
return sse_response(lifecycle_aware())
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Anatomy of an event
|
|
97
|
+
|
|
98
|
+
`publish()` takes the four spec fields:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
await stream.publish(
|
|
102
|
+
{"orderId": "abc", "status": "paid"}, # data: auto-JSON
|
|
103
|
+
event="order_update", # event: front-end listener name
|
|
104
|
+
id="42", # id: becomes Last-Event-ID (resume)
|
|
105
|
+
retry=3000, # retry: reconnect hint (ms)
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Field | What it does |
|
|
110
|
+
| --- | --- |
|
|
111
|
+
| `data` | Payload. String/bytes go raw; any object becomes JSON. |
|
|
112
|
+
| `event` | Event name — the front listens with `addEventListener(name)`. Without it, falls back to `"message"`. |
|
|
113
|
+
| `id` | Becomes `Last-Event-ID`; the browser resends it on reconnect so you can resume. |
|
|
114
|
+
| `retry` | Suggested reconnect delay (ms). |
|
|
115
|
+
|
|
116
|
+
`heartbeat_seconds` emits an SSE **comment** (`: keepalive`) while the
|
|
117
|
+
stream is idle so load-balancers don't cut the connection. Comments are
|
|
118
|
+
**invisible** to `EventSource` — they fire no listener, they just keep
|
|
119
|
+
the socket alive. `None` disables the heartbeat.
|
|
120
|
+
|
|
121
|
+
## Backpressure (bounded queue)
|
|
122
|
+
|
|
123
|
+
If a client **stops reading** (backgrounded tab, bad network) but the
|
|
124
|
+
producer keeps publishing, the `EventStream` queue would grow forever — a
|
|
125
|
+
classic memory leak. So the queue is **bounded**: `max_queue` (default
|
|
126
|
+
`1000`) plus an `overflow` policy that decides what to do when it fills.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from tempest_fastapi_sdk import EventStream
|
|
130
|
+
|
|
131
|
+
# Live ticker: a stale frame is worthless -> drop the oldest.
|
|
132
|
+
stream = EventStream(max_queue=500, overflow="drop_oldest")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
| `overflow` | When the queue fills | Use when |
|
|
136
|
+
| --- | --- | --- |
|
|
137
|
+
| `"drop_oldest"` (default) | Evict the **oldest** event | Live data: ticker, progress, telemetry — only the recent state matters. |
|
|
138
|
+
| `"drop_newest"` | Discard the **incoming** event | The start of the stream matters more than the end. |
|
|
139
|
+
| `"block"` | Hold `publish()` until a slot frees | Producer dedicated to **one** connection and losing an event is unacceptable. |
|
|
140
|
+
|
|
141
|
+
!!! danger "`block` can stall a shared producer"
|
|
142
|
+
With `overflow="block"`, a slow client **holds** `publish()`. If the
|
|
143
|
+
same producer feeds many clients (fan-out), one bad client stalls them
|
|
144
|
+
all. Only use `block` when the producer serves **one** connection.
|
|
145
|
+
|
|
146
|
+
The `close()` sentinel is **never** dropped or blocked — the stream always
|
|
147
|
+
terminates. `stream.dropped_events` counts how many events were lost to
|
|
148
|
+
overflow, so you can surface it in metrics/logs:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
if stream.dropped_events:
|
|
152
|
+
logger.warning("Slow SSE client: %d events dropped", stream.dropped_events)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
!!! tip "Back to the old behavior"
|
|
156
|
+
`max_queue=0` disables the bound (unbounded queue, pre-0.91). Only do
|
|
157
|
+
this if you're sure the producer stops together with the connection.
|
|
158
|
+
|
|
159
|
+
## Broadcast to many clients (`SSEBroker`)
|
|
160
|
+
|
|
161
|
+
`EventStream` is **one** connection. To send the same event to every
|
|
162
|
+
client of a channel (e.g. a user's devices, or a topic), the SDK ships
|
|
163
|
+
`SSEBroker` — a per-channel stream registry plus fan-out. The channel is
|
|
164
|
+
any string (a user id, a room slug...).
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# src/api/dependencies/resources.py
|
|
168
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
169
|
+
|
|
170
|
+
broker = SSEBroker() # singleton — keep on app.state and inject via Depends
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
# src/api/routers/feed.py
|
|
175
|
+
from uuid import UUID
|
|
176
|
+
|
|
177
|
+
from fastapi import APIRouter, Depends
|
|
178
|
+
from starlette.responses import StreamingResponse
|
|
179
|
+
|
|
180
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
181
|
+
|
|
182
|
+
router = APIRouter()
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@router.get("/feed")
|
|
186
|
+
async def feed(
|
|
187
|
+
user_id: UUID = Depends(get_current_user_id),
|
|
188
|
+
broker: SSEBroker = Depends(get_broker),
|
|
189
|
+
) -> StreamingResponse:
|
|
190
|
+
"""Subscribe the client to its user's channel."""
|
|
191
|
+
# register + sse_response + unregister-on-disconnect, all in one call.
|
|
192
|
+
return broker.response(str(user_id))
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
# From anywhere (queue handler, another endpoint):
|
|
196
|
+
# await broker.publish(str(user_id), {"text": "New order"}, event="notice")
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
!!! tip "`broker.response()` kills the stream leak"
|
|
200
|
+
`broker.response(channel)` does `register`, wraps it in `sse_response`
|
|
201
|
+
and wires an `on_disconnect` that calls `unregister` when the client
|
|
202
|
+
drops. No `try/finally` to forget — every disconnect cleans up its own
|
|
203
|
+
registration. `SSEBroker(max_queue=..., overflow=...)` applies the same
|
|
204
|
+
backpressure policy to every stream it opens.
|
|
205
|
+
|
|
206
|
+
### Multi-worker: Redis bridge (ready, no extra code)
|
|
207
|
+
|
|
208
|
+
An in-memory `SSEBroker` lives in **one** worker — with `--workers N` a
|
|
209
|
+
`publish` only reaches the clients pinned to that process. Pass a Redis
|
|
210
|
+
client and the **same `broker`** publishes via Redis `PUBLISH`; a
|
|
211
|
+
background task (`run()`) `PSUBSCRIBE`-s and relays to **each** worker's
|
|
212
|
+
local streams. Same call site, now horizontal:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
# src/api/app.py
|
|
216
|
+
import asyncio
|
|
217
|
+
from contextlib import asynccontextmanager
|
|
218
|
+
|
|
219
|
+
from fastapi import FastAPI
|
|
220
|
+
from redis.asyncio import Redis
|
|
221
|
+
|
|
222
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
223
|
+
|
|
224
|
+
redis = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
|
|
225
|
+
broker = SSEBroker(redis=redis, channel_prefix="sse")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@asynccontextmanager
|
|
229
|
+
async def lifespan(app: FastAPI):
|
|
230
|
+
task = asyncio.create_task(broker.run()) # subscribe Redis + fan out
|
|
231
|
+
try:
|
|
232
|
+
yield
|
|
233
|
+
finally:
|
|
234
|
+
await broker.aclose()
|
|
235
|
+
task.cancel()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
app = FastAPI(lifespan=lifespan)
|
|
239
|
+
# broker.publish(...) on any worker -> reaches ALL workers
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
!!! tip "Start simple, scale later"
|
|
243
|
+
Without Redis, `SSEBroker()` already covers a single process. When you
|
|
244
|
+
need multiple workers/hosts, just inject a Redis client and start
|
|
245
|
+
`run()` in the lifespan — no endpoint changes. `publish` becomes
|
|
246
|
+
cross-process for free.
|
|
247
|
+
|
|
248
|
+
## Authentication (cookie or query string)
|
|
249
|
+
|
|
250
|
+
Here's the SSE gotcha: the browser's native `EventSource` **can't** send
|
|
251
|
+
a header. No `Authorization: Bearer` on the handshake. So there are two
|
|
252
|
+
ways to authenticate the stream.
|
|
253
|
+
|
|
254
|
+
### Preferred: session cookie
|
|
255
|
+
|
|
256
|
+
If the front is on the **same origin** as the API, use an `HttpOnly`
|
|
257
|
+
cookie. The browser sends it on its own when you open with
|
|
258
|
+
`withCredentials`:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
const es = new EventSource("/api/feed", { withCredentials: true });
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
On the backend, the SDK already reads the token from the cookie — the
|
|
265
|
+
same seam as `make_auth_router`'s cookie delivery mode:
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
# src/api/dependencies/auth.py
|
|
269
|
+
from tempest_fastapi_sdk import JWTUtils, make_jwt_user_dependency
|
|
270
|
+
|
|
271
|
+
tokens = JWTUtils(secret=settings.JWT_SECRET)
|
|
272
|
+
|
|
273
|
+
current_user = make_jwt_user_dependency(
|
|
274
|
+
tokens,
|
|
275
|
+
load_user,
|
|
276
|
+
cookie_name="access_token", # <- EventSource + withCredentials
|
|
277
|
+
)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
!!! check "Why cookie is better"
|
|
281
|
+
The token stays out of the URL: no leak into access logs, browser
|
|
282
|
+
history or the `Referer` header. `HttpOnly` also keeps the token out
|
|
283
|
+
of JavaScript's reach (XSS defense). Prefer this path **whenever** the
|
|
284
|
+
origin is shared.
|
|
285
|
+
|
|
286
|
+
### Cookieless alternative: token in the query string
|
|
287
|
+
|
|
288
|
+
Without a session cookie (front on a **different origin**, a mobile app
|
|
289
|
+
opening a raw `EventSource`, an environment where `withCredentials` isn't
|
|
290
|
+
an option), pass the **access token** in the query string. As of v0.91
|
|
291
|
+
the dependency accepts this via `query_param`:
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# src/api/dependencies/auth.py
|
|
295
|
+
from tempest_fastapi_sdk import JWTUtils, make_jwt_user_dependency
|
|
296
|
+
|
|
297
|
+
tokens = JWTUtils(secret=settings.JWT_SECRET)
|
|
298
|
+
|
|
299
|
+
# Lookup order: header -> cookie -> query string.
|
|
300
|
+
current_user = make_jwt_user_dependency(
|
|
301
|
+
tokens,
|
|
302
|
+
load_user,
|
|
303
|
+
query_param="access_token", # <- ?access_token=<jwt>
|
|
304
|
+
)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
# src/api/routers/feed.py
|
|
309
|
+
from uuid import UUID
|
|
310
|
+
|
|
311
|
+
from fastapi import APIRouter, Depends
|
|
312
|
+
from starlette.responses import StreamingResponse
|
|
313
|
+
|
|
314
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
315
|
+
|
|
316
|
+
router = APIRouter()
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@router.get("/feed")
|
|
320
|
+
async def feed(
|
|
321
|
+
user: User = Depends(current_user), # resolves the JWT from the query
|
|
322
|
+
broker: SSEBroker = Depends(get_broker),
|
|
323
|
+
) -> StreamingResponse:
|
|
324
|
+
"""Authenticated stream without a cookie — token comes in the URL."""
|
|
325
|
+
return broker.response(str(user.id))
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
On the front:
|
|
329
|
+
|
|
330
|
+
```javascript
|
|
331
|
+
// The short-lived access token goes in the URL — never the refresh token.
|
|
332
|
+
const es = new EventSource(`/api/feed?access_token=${accessToken}`);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
!!! danger "Query strings leak — treat the token as disposable"
|
|
336
|
+
A token in the URL shows up in **access logs**, **history** and the
|
|
337
|
+
**`Referer`** header. Non-negotiable rules:
|
|
338
|
+
|
|
339
|
+
- **Short-lived access token** only (minutes). **Never** the refresh
|
|
340
|
+
token.
|
|
341
|
+
- Always over **TLS** (HTTPS).
|
|
342
|
+
- Strip the value from your proxy/server log format.
|
|
343
|
+
- Refresh through a normal endpoint (header/cookie), not the query.
|
|
344
|
+
|
|
345
|
+
!!! info "`query_param` also exists on the low-level dependency"
|
|
346
|
+
`make_bearer_token_dependency(tokens, query_param="access_token")`
|
|
347
|
+
returns just the decoded claims — use it when you build
|
|
348
|
+
`get_current_user` by hand. Same header → cookie → query order.
|
|
349
|
+
|
|
350
|
+
## Aligned with tempest-react-sdk
|
|
351
|
+
|
|
352
|
+
`tempest-react-sdk`'s `createEventStream` / `useEventStream`
|
|
353
|
+
([repo](https://github.com/mauriciobenjamin700/tempest-react-sdk))
|
|
354
|
+
consumes these endpoints with built-in exponential-backoff reconnect:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
import { createEventStream } from "@mauriciobenjamin700/tempest-react-sdk";
|
|
358
|
+
|
|
359
|
+
const stream = createEventStream<{ text: string }>("/feed", {
|
|
360
|
+
withCredentials: true, // sends the auth cookie on the handshake
|
|
361
|
+
namedEvents: ["notice"], // <- matches publish(event="notice")
|
|
362
|
+
onMessage: (m) => console.log(m.event, m.data), // data already JSON-parsed
|
|
363
|
+
});
|
|
364
|
+
// stream.close() to tear down; stream.reconnect() to force a reconnect
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
!!! tip "Heartbeat: comment vs a `ping` event"
|
|
368
|
+
`EventStream`'s heartbeat is a **comment** — `EventSource` ignores it,
|
|
369
|
+
so the react-sdk doesn't even need `heartbeatEvents`. If you prefer a
|
|
370
|
+
visible **named** heartbeat, publish
|
|
371
|
+
`await stream.publish("", event="ping")` and set
|
|
372
|
+
`heartbeatEvents: ["ping"]` on the front (its default).
|
|
373
|
+
|
|
374
|
+
Alignment points:
|
|
375
|
+
|
|
376
|
+
- `publish(event="x")` ↔ `namedEvents: ["x"]` + `onMessage`.
|
|
377
|
+
- non-string `data` becomes JSON ↔ the react default parser decodes JSON.
|
|
378
|
+
- `id=` ↔ `Last-Event-ID` resent on reconnect (resume where you left off).
|
|
379
|
+
- cookie auth ↔ `withCredentials: true`.
|
|
380
|
+
|
|
381
|
+
## Recap
|
|
382
|
+
|
|
383
|
+
- `EventStream` (one per connection) + `sse_response` — an SSE endpoint with headers set.
|
|
384
|
+
- Tie the producer to the connection with `on_disconnect=` (on `EventStream.response`, `sse_response` or `broker.response`) — no hand-rolled `try/finally`.
|
|
385
|
+
- Queue is **bounded** (`max_queue`, default `1000`) + `overflow` (`drop_oldest`/`drop_newest`/`block`) prevents leaks from slow clients; `dropped_events` counts the discards.
|
|
386
|
+
- `publish(data, event=, id=, retry=)` covers the 4 spec fields; non-string `data` becomes JSON.
|
|
387
|
+
- Heartbeat is a comment (invisible to EventSource); `None` disables it.
|
|
388
|
+
- Broadcast = `SSEBroker`; `broker.response(channel)` does register + response + unregister; multi-worker = pass a Redis client + start `broker.run()` in the lifespan.
|
|
389
|
+
- Auth: **cookie** (`cookie_name` + `withCredentials`) on the same origin; **query string** (`query_param`, short-lived access token over TLS only) for cookieless clients.
|
|
390
|
+
- `tempest-react-sdk` `createEventStream`/`useEventStream` consumes with reconnect; `namedEvents` ↔ `publish(event=)`.
|