tempest-fastapi-sdk 0.81.2__tar.gz → 0.82.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.81.2 → tempest_fastapi_sdk-0.82.0}/CHANGELOG.md +16 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/PKG-INFO +2 -2
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/README.md +1 -1
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/sse.en.md +64 -45
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/sse.md +64 -45
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/reference.md +1 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/pyproject.toml +1 -1
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/__init__.py +8 -2
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sse/__init__.py +2 -0
- tempest_fastapi_sdk-0.82.0/tempest_fastapi_sdk/sse/broker.py +208 -0
- tempest_fastapi_sdk-0.82.0/tests/sse/test_broker.py +80 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/uv.lock +1 -1
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/.github/workflows/ci.yml +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/.github/workflows/docs.yml +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/.gitignore +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/.python-version +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/CLAUDE.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/Makefile +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/architecture.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/architecture.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/changelog.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/changelog.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/contributing.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/contributing.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/index.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/index.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/installation.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/installation.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/index.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/index.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/api.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/api.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/business-rules.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/business-rules.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/domain.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/domain.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/flows.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/flows.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/index.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/learning/marketplace/index.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/migration.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/migration.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/admin.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/admin.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/audit-trail.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/audit-trail.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/auth-flow.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/auth-flow.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/br-helpers.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/br-helpers.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/cache.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/cache.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/cli.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/cli.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/database.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/database.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/deploy-safety.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/deploy-safety.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/downloads.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/downloads.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/email.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/email.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/feature-flags.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/feature-flags.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/fields.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/fields.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/http-client.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/http-client.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/http.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/http.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/idempotency.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/idempotency.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/index.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/index.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/logging.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/logging.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/metrics.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/metrics.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/mfa.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/mfa.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/multi-tenant.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/multi-tenant.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/observability.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/observability.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/offline-sync.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/offline-sync.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/outbox.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/outbox.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/queue-tasks.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/queue-tasks.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/realtime.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/realtime.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/refresh-tokens.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/refresh-tokens.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/security.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/security.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/sessions.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/sessions.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/storage.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/storage.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/stored-files.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/stored-files.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/testing.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/testing.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/typing.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/typing.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/uploads.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/uploads.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/utilities.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/utilities.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/webpush.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/webpush.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/websocket.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/recipes/websocket.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/reference.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/roadmap.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/roadmap.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/tutorial.en.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/docs/tutorial.md +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/mkdocs.yml +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/mkdocs_hooks/llmstxt.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/scripts/extract_recipe.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/config.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/discovery.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/session.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/site.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/admin/theme.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/server.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/static.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/guards.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/locale.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/service.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/activation.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cache/invalidation.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/Dockerfile.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/dockerignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/config.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/db.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/main.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/new.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/cli/user.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/core/context.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/core/enums.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/core/logging.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/core/typed.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/audit.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/backup.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/connection.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/repository.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/user_refresh_token_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/db/webpush_subscription_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/i18n.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/flags/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/flags/backends.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/flags/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/flags/service.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/py.typed +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/services/base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/services/file_mixin.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/settings/base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/testing/database.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/data/br_locations.json +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/download.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/email.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/fields.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/locations.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/log.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/password.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/webpush/router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/webpush/service.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_auth.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_discovery.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_forms.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_logs_nav.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_site.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_theme.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/admin/test_user_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_body_size.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_cookies.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_cors.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_csrf.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_dependencies_auth.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_graceful.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_handlers.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_health_router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_idempotency.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_jwt_dependency.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_logs_router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_oauth.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_prometheus.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_rate_limit_extras.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_request_id_middleware.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_role_dependency.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_server.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_static.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_tracing.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_webhooks.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/api/test_webhooks_rsa.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/test_guards.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/test_locale.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/test_refresh.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/test_refresh_db.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/auth/test_service.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cache/test_decorator.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cache/test_invalidation.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cache/test_redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_config.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_db.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_db_seed.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_generate.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_lint_strictness.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_main.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_secrets.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/cli/test_user.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/conftest.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/controllers/test_base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/core/test_context.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/core/test_enums.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/core/test_logging.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/core/test_typed.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_audit.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_backup.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_bulk_ops.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_connection.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_migrations.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_model.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_outbox.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_repository.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_safe_upgrade.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_slow_query.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/db/test_tenant.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/exceptions/test_exceptions.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/exceptions/test_i18n.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/flags/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/flags/test_flags.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/queue/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/schemas/test_base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/schemas/test_cursor_pagination.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/schemas/test_link_headers.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/schemas/test_pagination.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/schemas/test_response.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/services/test_base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/services/test_file_mixin.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/sessions/test_sessions.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/settings/test_base.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/settings/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/sse/test_event_stream.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/storage/test_minio_client.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/tasks/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/tasks/test_scheduler.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/testing/test_database.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_client_ip.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_datetime.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_dict.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_download.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_email.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_fields.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_http_client.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_jwt.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_lazy_extras.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_locations.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_log.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_metrics.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_password.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_regex.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_throttle.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/utils/test_upload.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/webpush/test_dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/webpush/test_router.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/webpush/test_schemas.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/webpush/test_service.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tests/websockets/test_hub_and_router.py +0 -0
|
@@ -5,6 +5,22 @@ 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.82.0] — 2026-06-28
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`SSEBroker` — multi-worker SSE fan-out.** The SSE recipe described a
|
|
13
|
+
Redis Pub/Sub bridge for broadcasting across workers but shipped no
|
|
14
|
+
primitive; `SSEBroker` is that primitive. It keeps a per-channel
|
|
15
|
+
registry of local `EventStream`s and fans `publish(channel, ...)` out
|
|
16
|
+
to them. Pass a `[cache]` Redis client and the same broker publishes
|
|
17
|
+
via Redis `PUBLISH` while a background `run()` task `PSUBSCRIBE`-s the
|
|
18
|
+
channel prefix and relays every message to each worker's local
|
|
19
|
+
streams — so `publish` becomes cross-process with no call-site change
|
|
20
|
+
(`register` / `unregister` / `publish` are identical in both modes).
|
|
21
|
+
Exported from `tempest_fastapi_sdk` and `tempest_fastapi_sdk.sse`. The
|
|
22
|
+
SSE recipe now shows the in-memory and Redis-lifespan setups.
|
|
23
|
+
|
|
8
24
|
## [0.81.2] — 2026-06-28
|
|
9
25
|
|
|
10
26
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempest-fastapi-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.82.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
|
|
@@ -250,7 +250,7 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
|
|
|
250
250
|
| `tempest_fastapi_sdk.services` | `BaseService` |
|
|
251
251
|
| `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` |
|
|
252
252
|
| `tempest_fastapi_sdk.admin` *(extra: `[admin]`)* | `AdminSite`, `AdminModel`, `make_admin_router`, `AdminAuthBackend`, `UserModelAuthBackend`, `AdminAuthError` |
|
|
253
|
-
| `tempest_fastapi_sdk.sse` | `EventStream`, `ServerSentEvent`, `sse_response` |
|
|
253
|
+
| `tempest_fastapi_sdk.sse` | `EventStream`, `SSEBroker` (multi-worker fan-out via Redis), `ServerSentEvent`, `sse_response` |
|
|
254
254
|
| `tempest_fastapi_sdk.cache` *(extra: `[cache]`)* | `AsyncRedisManager`, `cached` (with `namespace` / `tags`), `CacheInvalidator`, `namespace_registry_key`, `tag_registry_key` |
|
|
255
255
|
| `tempest_fastapi_sdk.flags` | `FeatureFlags`, `FeatureFlagBackend`, `MemoryFeatureFlagBackend`, `EnvFeatureFlagBackend`, `RedisFeatureFlagBackend`, `CompositeFeatureFlagBackend`, `make_flag_dependency`, `coerce_flag` |
|
|
256
256
|
| `tempest_fastapi_sdk.webpush` *(extra: `[webpush]`)* | `WebPushDispatcher`, `WebPushSubscriptionService`, `make_web_push_router`, `WebPushError`, `WebPushGoneError`, `WebPushSubscriptionSchema`, `WebPushKeysSchema`, `WebPushPayloadSchema` |
|
|
@@ -147,7 +147,7 @@ Since `0.7.1` every optional dependency is imported lazily at first instantiatio
|
|
|
147
147
|
| `tempest_fastapi_sdk.services` | `BaseService` |
|
|
148
148
|
| `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` |
|
|
149
149
|
| `tempest_fastapi_sdk.admin` *(extra: `[admin]`)* | `AdminSite`, `AdminModel`, `make_admin_router`, `AdminAuthBackend`, `UserModelAuthBackend`, `AdminAuthError` |
|
|
150
|
-
| `tempest_fastapi_sdk.sse` | `EventStream`, `ServerSentEvent`, `sse_response` |
|
|
150
|
+
| `tempest_fastapi_sdk.sse` | `EventStream`, `SSEBroker` (multi-worker fan-out via Redis), `ServerSentEvent`, `sse_response` |
|
|
151
151
|
| `tempest_fastapi_sdk.cache` *(extra: `[cache]`)* | `AsyncRedisManager`, `cached` (with `namespace` / `tags`), `CacheInvalidator`, `namespace_registry_key`, `tag_registry_key` |
|
|
152
152
|
| `tempest_fastapi_sdk.flags` | `FeatureFlags`, `FeatureFlagBackend`, `MemoryFeatureFlagBackend`, `EnvFeatureFlagBackend`, `RedisFeatureFlagBackend`, `CompositeFeatureFlagBackend`, `make_flag_dependency`, `coerce_flag` |
|
|
153
153
|
| `tempest_fastapi_sdk.webpush` *(extra: `[webpush]`)* | `WebPushDispatcher`, `WebPushSubscriptionService`, `make_web_push_router`, `WebPushError`, `WebPushGoneError`, `WebPushSubscriptionSchema`, `WebPushKeysSchema`, `WebPushPayloadSchema` |
|
|
@@ -93,77 +93,96 @@ stream is idle so load-balancers don't cut the connection. Comments are
|
|
|
93
93
|
**invisible** to `EventSource` — they fire no listener, they just keep
|
|
94
94
|
the socket alive. `None` disables the heartbeat.
|
|
95
95
|
|
|
96
|
-
## Broadcast to many clients
|
|
96
|
+
## Broadcast to many clients (`SSEBroker`)
|
|
97
97
|
|
|
98
|
-
`EventStream` is one connection. To send the same event to
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
`EventStream` is **one** connection. To send the same event to every
|
|
99
|
+
client of a channel (e.g. a user's devices, or a topic), the SDK ships
|
|
100
|
+
`SSEBroker` — a per-channel stream registry plus fan-out. The channel is
|
|
101
|
+
any string (a user id, a room slug...).
|
|
101
102
|
|
|
102
103
|
```python
|
|
103
|
-
# src/
|
|
104
|
-
from
|
|
105
|
-
|
|
106
|
-
from tempest_fastapi_sdk import EventStream
|
|
104
|
+
# src/api/dependencies/resources.py
|
|
105
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
107
106
|
|
|
107
|
+
broker = SSEBroker() # singleton — keep on app.state and inject via Depends
|
|
108
|
+
```
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
```python
|
|
111
|
+
# src/api/routers/feed.py
|
|
112
|
+
from uuid import UUID
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
from fastapi import APIRouter, Depends
|
|
115
|
+
from starlette.responses import StreamingResponse
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
"""Open a stream for a client and register it."""
|
|
117
|
-
stream = EventStream(heartbeat_seconds=15.0)
|
|
118
|
-
self._streams.setdefault(user_id, set()).add(stream)
|
|
119
|
-
return stream
|
|
117
|
+
from tempest_fastapi_sdk import sse_response
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
"""Drop a closed stream from the registry."""
|
|
123
|
-
streams = self._streams.get(user_id)
|
|
124
|
-
if streams:
|
|
125
|
-
streams.discard(stream)
|
|
126
|
-
if not streams:
|
|
127
|
-
del self._streams[user_id]
|
|
119
|
+
router = APIRouter()
|
|
128
120
|
|
|
129
|
-
async def publish_to_user(self, user_id: UUID, data: object, *, event: str) -> int:
|
|
130
|
-
"""Publish an event to all of a user's open streams."""
|
|
131
|
-
streams = self._streams.get(user_id, set())
|
|
132
|
-
for stream in streams:
|
|
133
|
-
await stream.publish(data, event=event)
|
|
134
|
-
return len(streams)
|
|
135
|
-
```
|
|
136
121
|
|
|
137
|
-
```python
|
|
138
|
-
# src/api/routers/feed.py
|
|
139
122
|
@router.get("/feed")
|
|
140
123
|
async def feed(
|
|
141
124
|
user_id: UUID = Depends(get_current_user_id),
|
|
142
|
-
|
|
125
|
+
broker: SSEBroker = Depends(get_broker),
|
|
143
126
|
) -> StreamingResponse:
|
|
144
|
-
"""Subscribe the client to its user's
|
|
145
|
-
|
|
127
|
+
"""Subscribe the client to its user's channel."""
|
|
128
|
+
channel = str(user_id)
|
|
129
|
+
stream = broker.register(channel)
|
|
146
130
|
|
|
147
131
|
async def lifecycle_aware() -> AsyncIterator[bytes]:
|
|
148
132
|
try:
|
|
149
133
|
async for chunk in stream.stream():
|
|
150
134
|
yield chunk
|
|
151
135
|
finally:
|
|
152
|
-
|
|
136
|
+
broker.unregister(channel, stream) # client left
|
|
153
137
|
|
|
154
138
|
return sse_response(lifecycle_aware())
|
|
155
139
|
|
|
156
140
|
|
|
157
141
|
# From anywhere (queue handler, another endpoint):
|
|
158
|
-
# await
|
|
142
|
+
# await broker.publish(str(user_id), {"text": "New order"}, event="notice")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Multi-worker: Redis bridge (ready, no extra code)
|
|
146
|
+
|
|
147
|
+
An in-memory `SSEBroker` lives in **one** worker — with `--workers N` a
|
|
148
|
+
`publish` only reaches the clients pinned to that process. Pass a Redis
|
|
149
|
+
client and the **same `broker`** publishes via Redis `PUBLISH`; a
|
|
150
|
+
background task (`run()`) `PSUBSCRIBE`-s and relays to **each** worker's
|
|
151
|
+
local streams. Same call site, now horizontal:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# src/api/app.py
|
|
155
|
+
import asyncio
|
|
156
|
+
from contextlib import asynccontextmanager
|
|
157
|
+
|
|
158
|
+
from fastapi import FastAPI
|
|
159
|
+
from redis.asyncio import Redis
|
|
160
|
+
|
|
161
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
162
|
+
|
|
163
|
+
redis = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
|
|
164
|
+
broker = SSEBroker(redis=redis, channel_prefix="sse")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@asynccontextmanager
|
|
168
|
+
async def lifespan(app: FastAPI):
|
|
169
|
+
task = asyncio.create_task(broker.run()) # subscribe Redis + fan out
|
|
170
|
+
try:
|
|
171
|
+
yield
|
|
172
|
+
finally:
|
|
173
|
+
await broker.aclose()
|
|
174
|
+
task.cancel()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
app = FastAPI(lifespan=lifespan)
|
|
178
|
+
# broker.publish(...) on any worker -> reaches ALL workers
|
|
159
179
|
```
|
|
160
180
|
|
|
161
|
-
!!!
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
channel and relays to its local streams.
|
|
181
|
+
!!! tip "Start simple, scale later"
|
|
182
|
+
Without Redis, `SSEBroker()` already covers a single process. When you
|
|
183
|
+
need multiple workers/hosts, just inject a Redis client and start
|
|
184
|
+
`run()` in the lifespan — no endpoint changes. `publish` becomes
|
|
185
|
+
cross-process for free.
|
|
167
186
|
|
|
168
187
|
## Aligned with tempest-react-sdk
|
|
169
188
|
|
|
@@ -202,5 +221,5 @@ Alignment points:
|
|
|
202
221
|
- Tie the producer to the connection lifecycle (`finally` → cancel/unregister).
|
|
203
222
|
- `publish(data, event=, id=, retry=)` covers the 4 spec fields; non-string `data` becomes JSON.
|
|
204
223
|
- Heartbeat is a comment (invisible to EventSource); `None` disables it.
|
|
205
|
-
- Broadcast =
|
|
224
|
+
- Broadcast = `SSEBroker` (per-channel stream registry); multi-worker = pass a Redis client + start `broker.run()` in the lifespan (same call site).
|
|
206
225
|
- `tempest-react-sdk` `createEventStream`/`useEventStream` consumes with reconnect; `namedEvents` ↔ `publish(event=)`.
|
|
@@ -93,77 +93,96 @@ stream fica ocioso, pra load-balancers não cortarem a conexão.
|
|
|
93
93
|
Comentários são **invisíveis** ao `EventSource` — não disparam nenhum
|
|
94
94
|
listener, só mantêm o socket vivo. `None` desliga o heartbeat.
|
|
95
95
|
|
|
96
|
-
## Broadcast pra vários clientes
|
|
96
|
+
## Broadcast pra vários clientes (`SSEBroker`)
|
|
97
97
|
|
|
98
|
-
`EventStream` é uma conexão. Pra mandar o mesmo evento pra todos
|
|
99
|
-
devices de um usuário
|
|
100
|
-
|
|
98
|
+
`EventStream` é **uma** conexão. Pra mandar o mesmo evento pra todos os
|
|
99
|
+
clientes de um canal (ex.: os devices de um usuário, ou um tópico), o SDK
|
|
100
|
+
traz o `SSEBroker` — registro de streams por canal + fan-out. O canal é
|
|
101
|
+
uma string qualquer (id de usuário, slug de sala...).
|
|
101
102
|
|
|
102
103
|
```python
|
|
103
|
-
# src/
|
|
104
|
-
from
|
|
105
|
-
|
|
106
|
-
from tempest_fastapi_sdk import EventStream
|
|
104
|
+
# src/api/dependencies/resources.py
|
|
105
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
107
106
|
|
|
107
|
+
broker = SSEBroker() # singleton — guarde em app.state e injete via Depends
|
|
108
|
+
```
|
|
108
109
|
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
```python
|
|
111
|
+
# src/api/routers/feed.py
|
|
112
|
+
from uuid import UUID
|
|
111
113
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
from fastapi import APIRouter, Depends
|
|
115
|
+
from starlette.responses import StreamingResponse
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
"""Abre um stream para um cliente e o registra."""
|
|
117
|
-
stream = EventStream(heartbeat_seconds=15.0)
|
|
118
|
-
self._streams.setdefault(user_id, set()).add(stream)
|
|
119
|
-
return stream
|
|
117
|
+
from tempest_fastapi_sdk import sse_response
|
|
120
118
|
|
|
121
|
-
|
|
122
|
-
"""Remove um stream fechado do registro."""
|
|
123
|
-
streams = self._streams.get(user_id)
|
|
124
|
-
if streams:
|
|
125
|
-
streams.discard(stream)
|
|
126
|
-
if not streams:
|
|
127
|
-
del self._streams[user_id]
|
|
119
|
+
router = APIRouter()
|
|
128
120
|
|
|
129
|
-
async def publish_to_user(self, user_id: UUID, data: object, *, event: str) -> int:
|
|
130
|
-
"""Publica um evento em todos os streams abertos de um usuário."""
|
|
131
|
-
streams = self._streams.get(user_id, set())
|
|
132
|
-
for stream in streams:
|
|
133
|
-
await stream.publish(data, event=event)
|
|
134
|
-
return len(streams)
|
|
135
|
-
```
|
|
136
121
|
|
|
137
|
-
```python
|
|
138
|
-
# src/api/routers/feed.py
|
|
139
122
|
@router.get("/feed")
|
|
140
123
|
async def feed(
|
|
141
124
|
user_id: UUID = Depends(get_current_user_id),
|
|
142
|
-
|
|
125
|
+
broker: SSEBroker = Depends(get_broker),
|
|
143
126
|
) -> StreamingResponse:
|
|
144
|
-
"""Inscreve o cliente no
|
|
145
|
-
|
|
127
|
+
"""Inscreve o cliente no canal do seu usuário."""
|
|
128
|
+
channel = str(user_id)
|
|
129
|
+
stream = broker.register(channel)
|
|
146
130
|
|
|
147
131
|
async def lifecycle_aware() -> AsyncIterator[bytes]:
|
|
148
132
|
try:
|
|
149
133
|
async for chunk in stream.stream():
|
|
150
134
|
yield chunk
|
|
151
135
|
finally:
|
|
152
|
-
|
|
136
|
+
broker.unregister(channel, stream) # cliente saiu
|
|
153
137
|
|
|
154
138
|
return sse_response(lifecycle_aware())
|
|
155
139
|
|
|
156
140
|
|
|
157
141
|
# De qualquer lugar (handler de fila, outro endpoint):
|
|
158
|
-
# await
|
|
142
|
+
# await broker.publish(str(user_id), {"text": "Novo pedido"}, event="notice")
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Multi-worker: bridge Redis (pronto, sem código extra)
|
|
146
|
+
|
|
147
|
+
O `SSEBroker` em memória vive em **um** worker — com `--workers N`, um
|
|
148
|
+
`publish` só alcança os clientes presos naquele processo. Passe um client
|
|
149
|
+
Redis e o **mesmo `broker`** passa a publicar via Redis `PUBLISH`; uma
|
|
150
|
+
task de fundo (`run()`) faz `PSUBSCRIBE` e repassa pros streams locais de
|
|
151
|
+
**cada** worker. Mesmo call site, agora horizontal:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
# src/api/app.py
|
|
155
|
+
import asyncio
|
|
156
|
+
from contextlib import asynccontextmanager
|
|
157
|
+
|
|
158
|
+
from fastapi import FastAPI
|
|
159
|
+
from redis.asyncio import Redis
|
|
160
|
+
|
|
161
|
+
from tempest_fastapi_sdk import SSEBroker
|
|
162
|
+
|
|
163
|
+
redis = Redis.from_url("redis://localhost:6379/0", decode_responses=True)
|
|
164
|
+
broker = SSEBroker(redis=redis, channel_prefix="sse")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@asynccontextmanager
|
|
168
|
+
async def lifespan(app: FastAPI):
|
|
169
|
+
task = asyncio.create_task(broker.run()) # assina o Redis e faz o fan-out
|
|
170
|
+
try:
|
|
171
|
+
yield
|
|
172
|
+
finally:
|
|
173
|
+
await broker.aclose()
|
|
174
|
+
task.cancel()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
app = FastAPI(lifespan=lifespan)
|
|
178
|
+
# broker.publish(...) em qualquer worker -> chega em TODOS os workers
|
|
159
179
|
```
|
|
160
180
|
|
|
161
|
-
!!!
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
repassa pros seus streams locais.
|
|
181
|
+
!!! tip "Comece simples, escale depois"
|
|
182
|
+
Sem Redis, `SSEBroker()` já resolve um processo. Quando precisar de
|
|
183
|
+
múltiplos workers/hosts, só injete o client Redis e suba o `run()` no
|
|
184
|
+
lifespan — nenhum endpoint muda. O `publish` se torna cross-process de
|
|
185
|
+
graça.
|
|
167
186
|
|
|
168
187
|
## Alinhado com o tempest-react-sdk
|
|
169
188
|
|
|
@@ -202,5 +221,5 @@ Pontos de alinhamento:
|
|
|
202
221
|
- Amarre o produtor ao ciclo de vida da conexão (`finally` → cancela/desregistra).
|
|
203
222
|
- `publish(data, event=, id=, retry=)` cobre os 4 campos do spec; `data` não-string vira JSON.
|
|
204
223
|
- Heartbeat é comentário (invisível ao EventSource); `None` desliga.
|
|
205
|
-
- Broadcast = registro de streams
|
|
224
|
+
- Broadcast = `SSEBroker` (registro de streams por canal); multi-worker = passe um client Redis + suba `broker.run()` no lifespan (mesmo call site).
|
|
206
225
|
- `tempest-react-sdk` `createEventStream`/`useEventStream` consome com reconnect; `namedEvents` ↔ `publish(event=)`.
|
|
@@ -241,6 +241,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
|
|
|
241
241
|
### `tempest_fastapi_sdk.sse`
|
|
242
242
|
|
|
243
243
|
::: tempest_fastapi_sdk.sse.event_stream.EventStream
|
|
244
|
+
::: tempest_fastapi_sdk.sse.broker.SSEBroker
|
|
244
245
|
::: tempest_fastapi_sdk.sse.event_stream.ServerSentEvent
|
|
245
246
|
::: tempest_fastapi_sdk.sse.event_stream.sse_response
|
|
246
247
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tempest-fastapi-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.82.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"
|
|
@@ -233,7 +233,12 @@ from tempest_fastapi_sdk.settings import (
|
|
|
233
233
|
WebPushSettings,
|
|
234
234
|
WebSocketSettings,
|
|
235
235
|
)
|
|
236
|
-
from tempest_fastapi_sdk.sse import
|
|
236
|
+
from tempest_fastapi_sdk.sse import (
|
|
237
|
+
EventStream,
|
|
238
|
+
ServerSentEvent,
|
|
239
|
+
SSEBroker,
|
|
240
|
+
sse_response,
|
|
241
|
+
)
|
|
237
242
|
from tempest_fastapi_sdk.storage import AsyncMinIOClient, ObjectStat
|
|
238
243
|
from tempest_fastapi_sdk.utils import (
|
|
239
244
|
CEP,
|
|
@@ -346,7 +351,7 @@ from tempest_fastapi_sdk.websockets import (
|
|
|
346
351
|
make_websocket_router,
|
|
347
352
|
)
|
|
348
353
|
|
|
349
|
-
__version__: str = "0.
|
|
354
|
+
__version__: str = "0.82.0"
|
|
350
355
|
|
|
351
356
|
__all__: list[str] = [
|
|
352
357
|
"BASE_COLUMN_ORDER",
|
|
@@ -517,6 +522,7 @@ __all__: list[str] = [
|
|
|
517
522
|
"Region",
|
|
518
523
|
"RequestIDMiddleware",
|
|
519
524
|
"RetryPolicy",
|
|
525
|
+
"SSEBroker",
|
|
520
526
|
"SameSite",
|
|
521
527
|
"ServerSentEvent",
|
|
522
528
|
"ServerSettings",
|
{tempest_fastapi_sdk-0.81.2 → tempest_fastapi_sdk-0.82.0}/tempest_fastapi_sdk/sse/__init__.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Server-Sent Events helpers built on top of Starlette streaming."""
|
|
2
2
|
|
|
3
|
+
from tempest_fastapi_sdk.sse.broker import SSEBroker
|
|
3
4
|
from tempest_fastapi_sdk.sse.event_stream import (
|
|
4
5
|
EventStream,
|
|
5
6
|
ServerSentEvent,
|
|
@@ -8,6 +9,7 @@ from tempest_fastapi_sdk.sse.event_stream import (
|
|
|
8
9
|
|
|
9
10
|
__all__: list[str] = [
|
|
10
11
|
"EventStream",
|
|
12
|
+
"SSEBroker",
|
|
11
13
|
"ServerSentEvent",
|
|
12
14
|
"sse_response",
|
|
13
15
|
]
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Multi-worker SSE fan-out: local registry + optional Redis pub/sub bridge.
|
|
2
|
+
|
|
3
|
+
:class:`EventStream` feeds **one** connection. To broadcast the same
|
|
4
|
+
event to every client subscribed to a channel — across multiple
|
|
5
|
+
Uvicorn/Gunicorn workers — you need a fan-out layer plus a cross-process
|
|
6
|
+
transport. :class:`SSEBroker` is both:
|
|
7
|
+
|
|
8
|
+
* **Single process** (``redis=None``) — keeps a registry of local
|
|
9
|
+
streams per channel; :meth:`publish` fans out to them in-process.
|
|
10
|
+
* **Multi-worker** (``redis=<client>``) — :meth:`publish` goes through
|
|
11
|
+
Redis ``PUBLISH``; a background :meth:`run` task ``PSUBSCRIBE``-s the
|
|
12
|
+
channel prefix and re-fans every message to the worker's local
|
|
13
|
+
streams. Run :meth:`run` from the app lifespan and stop it with
|
|
14
|
+
:meth:`aclose`.
|
|
15
|
+
|
|
16
|
+
The same call site (``register`` / ``publish`` / ``unregister``) works in
|
|
17
|
+
both modes, so a service starts single-process and gains horizontal
|
|
18
|
+
scale by passing a Redis client — no code change.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
from typing import TYPE_CHECKING, Any
|
|
25
|
+
|
|
26
|
+
from tempest_fastapi_sdk.sse.event_stream import EventStream, ServerSentEvent
|
|
27
|
+
|
|
28
|
+
if TYPE_CHECKING:
|
|
29
|
+
from redis.asyncio import Redis
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SSEBroker:
|
|
33
|
+
"""Channel-based SSE fan-out, optionally bridged over Redis pub/sub.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
channel_prefix (str): Redis key prefix for published channels
|
|
37
|
+
(``"<prefix>:<channel>"``). Also the ``PSUBSCRIBE`` pattern
|
|
38
|
+
root. Ignored in single-process mode.
|
|
39
|
+
heartbeat_seconds (float | None): Idle interval passed to every
|
|
40
|
+
:class:`EventStream` created by :meth:`register`.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
*,
|
|
46
|
+
redis: Redis | None = None,
|
|
47
|
+
channel_prefix: str = "sse",
|
|
48
|
+
heartbeat_seconds: float | None = 15.0,
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Initialize the broker.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
redis (Redis | None): Async Redis client (from the ``[cache]``
|
|
54
|
+
extra). ``None`` runs single-process (local fan-out only).
|
|
55
|
+
channel_prefix (str): Prefix for Redis channels.
|
|
56
|
+
heartbeat_seconds (float | None): Idle heartbeat for streams.
|
|
57
|
+
"""
|
|
58
|
+
self._redis: Redis | None = redis
|
|
59
|
+
self.channel_prefix: str = channel_prefix
|
|
60
|
+
self.heartbeat_seconds: float | None = heartbeat_seconds
|
|
61
|
+
self._channels: dict[str, set[EventStream]] = {}
|
|
62
|
+
self._pubsub: Any = None
|
|
63
|
+
|
|
64
|
+
def register(self, channel: str) -> EventStream:
|
|
65
|
+
"""Open a local stream subscribed to ``channel`` and track it.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
channel (str): The logical channel (e.g. a user id or topic).
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
EventStream: A fresh stream to hand to
|
|
72
|
+
:func:`tempest_fastapi_sdk.sse_response`.
|
|
73
|
+
"""
|
|
74
|
+
stream = EventStream(heartbeat_seconds=self.heartbeat_seconds)
|
|
75
|
+
self._channels.setdefault(channel, set()).add(stream)
|
|
76
|
+
return stream
|
|
77
|
+
|
|
78
|
+
def unregister(self, channel: str, stream: EventStream) -> None:
|
|
79
|
+
"""Drop a closed stream from ``channel``.
|
|
80
|
+
|
|
81
|
+
Call this from the response generator's ``finally`` so a
|
|
82
|
+
disconnected client is forgotten.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
channel (str): The channel the stream was registered under.
|
|
86
|
+
stream (EventStream): The stream to remove.
|
|
87
|
+
"""
|
|
88
|
+
streams = self._channels.get(channel)
|
|
89
|
+
if streams:
|
|
90
|
+
streams.discard(stream)
|
|
91
|
+
if not streams:
|
|
92
|
+
del self._channels[channel]
|
|
93
|
+
|
|
94
|
+
def local_subscribers(self, channel: str) -> int:
|
|
95
|
+
"""Return how many local streams are open on ``channel``.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
channel (str): The channel to inspect.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
int: The local subscriber count (this worker only).
|
|
102
|
+
"""
|
|
103
|
+
return len(self._channels.get(channel, set()))
|
|
104
|
+
|
|
105
|
+
async def publish(
|
|
106
|
+
self,
|
|
107
|
+
channel: str,
|
|
108
|
+
data: Any = "",
|
|
109
|
+
*,
|
|
110
|
+
event: str | None = None,
|
|
111
|
+
id: str | None = None,
|
|
112
|
+
retry: int | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
"""Publish an event to every subscriber of ``channel``.
|
|
115
|
+
|
|
116
|
+
In Redis mode the event is ``PUBLISH``-ed and delivered to all
|
|
117
|
+
workers (including this one) by their :meth:`run` loop. In
|
|
118
|
+
single-process mode it fans out to local streams immediately.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
channel (str): Target channel.
|
|
122
|
+
data (Any): Payload (string, bytes or JSON-serializable).
|
|
123
|
+
event (str | None): Optional SSE event name.
|
|
124
|
+
id (str | None): Optional ``Last-Event-ID``.
|
|
125
|
+
retry (int | None): Optional reconnect hint (ms).
|
|
126
|
+
"""
|
|
127
|
+
if self._redis is None:
|
|
128
|
+
await self._emit_local(
|
|
129
|
+
channel,
|
|
130
|
+
ServerSentEvent(data=data, event=event, id=id, retry=retry),
|
|
131
|
+
)
|
|
132
|
+
return
|
|
133
|
+
envelope = json.dumps(
|
|
134
|
+
{"data": data, "event": event, "id": id, "retry": retry},
|
|
135
|
+
default=str,
|
|
136
|
+
)
|
|
137
|
+
await self._redis.publish(self._key(channel), envelope)
|
|
138
|
+
|
|
139
|
+
async def run(self) -> None:
|
|
140
|
+
"""Consume Redis pub/sub and fan messages to local streams.
|
|
141
|
+
|
|
142
|
+
Long-running: start it as a background task in the app lifespan
|
|
143
|
+
and cancel it (or call :meth:`aclose`) on shutdown. A no-op in
|
|
144
|
+
single-process mode.
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
RuntimeError: If called without a Redis client.
|
|
148
|
+
"""
|
|
149
|
+
if self._redis is None:
|
|
150
|
+
raise RuntimeError("SSEBroker.run() requires a Redis client.")
|
|
151
|
+
pubsub: Any = self._redis.pubsub()
|
|
152
|
+
self._pubsub = pubsub
|
|
153
|
+
await pubsub.psubscribe(f"{self.channel_prefix}:*")
|
|
154
|
+
try:
|
|
155
|
+
async for message in pubsub.listen():
|
|
156
|
+
if message.get("type") != "pmessage":
|
|
157
|
+
continue
|
|
158
|
+
await self._dispatch_raw(message["channel"], message["data"])
|
|
159
|
+
finally:
|
|
160
|
+
await pubsub.aclose()
|
|
161
|
+
self._pubsub = None
|
|
162
|
+
|
|
163
|
+
async def aclose(self) -> None:
|
|
164
|
+
"""Stop the pub/sub subscription opened by :meth:`run`."""
|
|
165
|
+
if self._pubsub is not None:
|
|
166
|
+
await self._pubsub.aclose()
|
|
167
|
+
self._pubsub = None
|
|
168
|
+
|
|
169
|
+
def _key(self, channel: str) -> str:
|
|
170
|
+
"""Return the Redis channel key for ``channel``."""
|
|
171
|
+
return f"{self.channel_prefix}:{channel}"
|
|
172
|
+
|
|
173
|
+
async def _dispatch_raw(self, channel_key: str | bytes, raw: str | bytes) -> None:
|
|
174
|
+
"""Decode a Redis pub/sub frame and fan it to local streams.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
channel_key (str | bytes): The Redis channel
|
|
178
|
+
(``<prefix>:<channel>``), as delivered by pub/sub.
|
|
179
|
+
raw (str | bytes): The JSON envelope payload.
|
|
180
|
+
"""
|
|
181
|
+
key = channel_key.decode() if isinstance(channel_key, bytes) else channel_key
|
|
182
|
+
body = raw.decode() if isinstance(raw, bytes) else raw
|
|
183
|
+
channel = key.removeprefix(f"{self.channel_prefix}:")
|
|
184
|
+
payload: dict[str, Any] = json.loads(body)
|
|
185
|
+
await self._emit_local(
|
|
186
|
+
channel,
|
|
187
|
+
ServerSentEvent(
|
|
188
|
+
data=payload.get("data", ""),
|
|
189
|
+
event=payload.get("event"),
|
|
190
|
+
id=payload.get("id"),
|
|
191
|
+
retry=payload.get("retry"),
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
async def _emit_local(self, channel: str, event: ServerSentEvent) -> None:
|
|
196
|
+
"""Enqueue ``event`` on every local stream of ``channel``.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
channel (str): The channel to fan out to.
|
|
200
|
+
event (ServerSentEvent): The event to deliver.
|
|
201
|
+
"""
|
|
202
|
+
for stream in tuple(self._channels.get(channel, set())):
|
|
203
|
+
await stream.publish_event(event)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__: list[str] = [
|
|
207
|
+
"SSEBroker",
|
|
208
|
+
]
|