tempest-fastapi-sdk 0.63.0__tar.gz → 0.64.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.63.0 → tempest_fastapi_sdk-0.64.0}/CHANGELOG.md +27 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/PKG-INFO +1 -1
- tempest_fastapi_sdk-0.64.0/docs/recipes/stored-files.en.md +129 -0
- tempest_fastapi_sdk-0.64.0/docs/recipes/stored-files.md +128 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/reference.md +1 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/mkdocs.yml +1 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/pyproject.toml +1 -1
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/__init__.py +10 -2
- tempest_fastapi_sdk-0.64.0/tempest_fastapi_sdk/services/__init__.py +15 -0
- tempest_fastapi_sdk-0.64.0/tempest_fastapi_sdk/services/file_mixin.py +221 -0
- tempest_fastapi_sdk-0.64.0/tests/services/test_file_mixin.py +215 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/uv.lock +1 -1
- tempest_fastapi_sdk-0.63.0/tempest_fastapi_sdk/services/__init__.py +0 -7
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.github/workflows/ci.yml +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.github/workflows/docs.yml +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.github/workflows/release-pypi.yml +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.gitignore +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/.python-version +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/CLAUDE.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/Makefile +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/README.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/architecture.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/architecture.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/changelog.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/changelog.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/contributing.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/contributing.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/index.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/index.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/installation.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/installation.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/index.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/index.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/api.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/api.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/business-rules.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/business-rules.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/domain.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/domain.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/flows.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/flows.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/index.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/learning/marketplace/index.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/migration.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/migration.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/admin.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/admin.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/audit-trail.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/audit-trail.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/auth-flow.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/auth-flow.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/br-helpers.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/br-helpers.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cache.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cache.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cli.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/cli.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/database.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/database.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/deploy-safety.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/deploy-safety.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/downloads.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/downloads.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/email.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/email.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/feature-flags.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/feature-flags.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http-client.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http-client.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/http.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/idempotency.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/idempotency.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/index.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/index.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/logging.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/logging.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/metrics.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/metrics.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/mfa.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/mfa.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/multi-tenant.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/multi-tenant.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/observability.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/observability.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/offline-sync.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/offline-sync.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/outbox.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/outbox.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/queue-tasks.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/queue-tasks.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/realtime.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/realtime.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/security.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/security.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/sessions.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/sessions.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/storage.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/storage.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/testing.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/testing.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/uploads.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/uploads.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/utilities.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/utilities.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/webpush.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/webpush.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/websocket.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/recipes/websocket.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/reference.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/roadmap.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/roadmap.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/tutorial.en.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/docs/tutorial.md +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/scripts/extract_recipe.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/auth.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/config.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/forms.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/session.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/site.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/static/admin.css +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/base.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/dashboard.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/detail.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/form.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/list.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/login.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/logs.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/admin/templates/mfa.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/cookies.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/dependencies/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/dependencies/auth.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/handlers.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/body_size.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/cors.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/csrf.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/graceful.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/idempotency.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/middlewares/request_id.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/oauth.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/health.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/logs.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/metrics.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/routers/tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/server.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/static.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/tracing.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/api/webhooks.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/guards.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/locale.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/page_renderer.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/schemas.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/service.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/activation.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/en-US/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_error.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/activation_success.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_error.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_form.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/auth/templates/pt-BR/password_reset_success.html +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/decorator.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/invalidation.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cache/redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/README.md.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/env.example.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/gitignore.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/main.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/pyproject.toml.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/app.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/auth.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/dependencies/resources.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/api/routers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/controllers/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/core/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/core/exceptions.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/core/settings.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/models/user.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/db/repositories/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/schemas/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/server.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/services/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/src/utils/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/tests/__init__.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/_templates/tests/test_smoke.py.tmpl +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/db.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/generate.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/lint.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/main.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/new.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/secrets.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/src_layers.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/cli/user.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/controllers/base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/context.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/enums.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/core/logging.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/_alembic_templates/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/_alembic_templates/env.py.template +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/audit.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/connection.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/migrations.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/mixins.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/model.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/outbox.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/repository.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/slow_query.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/tenant.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/user_model.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/user_recovery_code_model.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/db/user_token_model.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/conflict.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/forbidden.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/i18n.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/jwt.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/not_found.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/too_many_requests.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/unauthorized.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/upload.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/exceptions/validation.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/backends.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/flags/service.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/py.typed +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/queue/manager.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/link_headers.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/logs.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/pagination.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/schemas/response.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/services/base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/dependencies.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/middleware.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/schemas.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/service.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sessions/store.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/settings/base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/settings/mixins.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/sse/event_stream.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/storage/minio_client.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/tasks/manager.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/tasks/scheduler.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/testing/database.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/client_ip.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/data/br_locations.json +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/datetime.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/dict.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/download.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/email.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/http_client.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/jwt.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/locations.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/log.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/metrics.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/password.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/regex.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/throttle.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/totp.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/utils/upload.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/webpush/dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/webpush/schemas.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/hub.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tempest_fastapi_sdk/websockets/schemas.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_auth.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_forms.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_logs_nav.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_site.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/admin/test_user_model.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_body_size.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_cookies.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_cors.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_csrf.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_dependencies_auth.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_graceful.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_handlers.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_health_router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_idempotency.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_jwt_dependency.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_logs_router.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_oauth.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_prometheus.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_rate_limit.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_rate_limit_extras.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_request_id_middleware.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_role_dependency.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_server.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_static.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_tool_spec.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_tracing.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_webhooks.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/api/test_webhooks_rsa.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_guards.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_locale.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_mfa.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/auth/test_service.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/test_decorator.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/test_invalidation.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cache/test_redis_manager.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_db.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_db_seed.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_docker_compose.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_generate.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_main.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_secrets.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/cli/test_user.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/conftest.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/controllers/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/controllers/test_base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/test_context.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/test_enums.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/core/test_logging.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_alembic_hooks.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_audit.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_bulk_ops.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_connection.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_migrations.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_model.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_outbox.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_repository.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_safe_upgrade.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_slow_query.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/db/test_tenant.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/exceptions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/exceptions/test_exceptions.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/exceptions/test_i18n.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/flags/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/flags/test_flags.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/queue/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/queue/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_cursor_pagination.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_link_headers.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_pagination.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/schemas/test_response.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/services/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/services/test_base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sessions/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sessions/test_sessions.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/settings/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/settings/test_base.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/settings/test_mixins.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sse/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/sse/test_event_stream.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/storage/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/storage/test_minio_client.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/tasks/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/tasks/test_manager.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/tasks/test_scheduler.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/testing/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/testing/test_database.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_client_ip.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_datetime.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_dict.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_download.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_email.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_http_client.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_jwt.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_lazy_extras.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_locations.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_log.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_metrics.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_opaque_token.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_password.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_regex.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_storage_backends.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_throttle.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/utils/test_upload.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/webpush/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/webpush/test_dispatcher.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/webpush/test_schemas.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/websockets/__init__.py +0 -0
- {tempest_fastapi_sdk-0.63.0 → tempest_fastapi_sdk-0.64.0}/tests/websockets/test_hub_and_router.py +0 -0
|
@@ -5,6 +5,33 @@ 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.64.0] — 2026-06-21
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **`StoredFileServiceMixin[Model]`** — a service mixin that encodes the
|
|
13
|
+
single-key stored-file flow once, parameterized by field name:
|
|
14
|
+
- `set_file(ref, file, *, field, subdir=..., filename=..., keep_original_name=...)`
|
|
15
|
+
resolves the entity (detach-safe), uploads the new file and deletes the
|
|
16
|
+
old one via `UploadUtils.replace` (new written before old deleted),
|
|
17
|
+
writes the key back and commits.
|
|
18
|
+
- `clear_file(ref, *, field)` deletes the object and nulls the field
|
|
19
|
+
(no-op, no commit, when the field is already empty).
|
|
20
|
+
- `file_url(key, *, expires=...)` returns a presigned download URL, or
|
|
21
|
+
`None` for an empty key.
|
|
22
|
+
|
|
23
|
+
Removes the ~13-line boilerplate every service reimplements for avatars,
|
|
24
|
+
banners, covers and attachments. Reads its `upload_utils` and `storage`
|
|
25
|
+
collaborators off `self`, so the owning service keeps configuration (size
|
|
26
|
+
limits, allowed types, bucket). Covers the common "one key field →
|
|
27
|
+
presigned URL" case; resize/thumbnail pipelines, multi-variant assets and
|
|
28
|
+
galleries are out of scope (compose `UploadUtils` directly). See the
|
|
29
|
+
**Arquivo no serviço (mixin)** recipe.
|
|
30
|
+
- **`SupportsUpload`** and **`SupportsPresign`** — structural-typing
|
|
31
|
+
protocols describing the collaborators `StoredFileServiceMixin` needs
|
|
32
|
+
(satisfied by `UploadUtils` and `AsyncMinIOClient`), so importing the
|
|
33
|
+
mixin never pulls the optional `[upload]` / `[minio]` extras.
|
|
34
|
+
|
|
8
35
|
## [0.63.0] — 2026-06-21
|
|
9
36
|
|
|
10
37
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tempest-fastapi-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.64.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
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# File on the service — `StoredFileServiceMixin`
|
|
2
|
+
|
|
3
|
+
An entity almost always carries **one** storage key: the user's avatar, an
|
|
4
|
+
event banner, a product cover, an attachment. And every service that owns one
|
|
5
|
+
rewrites the same dance by hand:
|
|
6
|
+
|
|
7
|
+
1. resolve the entity,
|
|
8
|
+
2. upload the new file and delete the old one,
|
|
9
|
+
3. write the new key onto the model,
|
|
10
|
+
4. `commit`,
|
|
11
|
+
5. hand out a temporary download URL.
|
|
12
|
+
|
|
13
|
+
`StoredFileServiceMixin` does this flow **once**, parameterized by the
|
|
14
|
+
**field name** — so a single service can manage several file fields without
|
|
15
|
+
duplication. It builds on top of [`UploadUtils`](uploads.md) (upload +
|
|
16
|
+
validation) and [`AsyncMinIOClient`](storage.md) (presigned URL); it needs the
|
|
17
|
+
`[upload]` and `[minio]` extras.
|
|
18
|
+
|
|
19
|
+
!!! info "Scope: the common case"
|
|
20
|
+
Covers **one key per field → presigned URL**. Thumbnails, variants
|
|
21
|
+
(S/M/L), public/CDN buckets and galleries (one-to-many) are out of scope —
|
|
22
|
+
compose `UploadUtils` directly for those.
|
|
23
|
+
|
|
24
|
+
## Mixing it into your service
|
|
25
|
+
|
|
26
|
+
The mixin builds nothing: it reads two collaborators off `self` —
|
|
27
|
+
`upload_utils` and `storage`. The service stays in charge of configuration
|
|
28
|
+
(size, allowed types, bucket):
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from tempest_fastapi_sdk import (
|
|
32
|
+
AsyncMinIOClient,
|
|
33
|
+
BaseService,
|
|
34
|
+
StoredFileServiceMixin,
|
|
35
|
+
UploadUtils,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from src.db.models import UserModel
|
|
39
|
+
from src.db.repositories import UserRepository
|
|
40
|
+
from src.schemas import UserResponseSchema
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class UserService(
|
|
44
|
+
BaseService[UserRepository, UserResponseSchema],
|
|
45
|
+
StoredFileServiceMixin[UserModel],
|
|
46
|
+
):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
repository: UserRepository,
|
|
50
|
+
storage: AsyncMinIOClient,
|
|
51
|
+
upload_utils: UploadUtils,
|
|
52
|
+
) -> None:
|
|
53
|
+
super().__init__(repository)
|
|
54
|
+
self.storage = storage
|
|
55
|
+
self.upload_utils = upload_utils
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Base order matters: `BaseService` brings the `repository`; the mixin only
|
|
59
|
+
adds the file methods on top.
|
|
60
|
+
|
|
61
|
+
## Swap the file — `set_file`
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
async def update_profile_picture(
|
|
65
|
+
self, user: UUID | UserModel, image: UploadFile
|
|
66
|
+
) -> UserResponseSchema:
|
|
67
|
+
"""Upload the new picture, delete the old one, return the profile + URL."""
|
|
68
|
+
updated = await self.set_file(
|
|
69
|
+
user, image, field="profile_picture", subdir="profiles"
|
|
70
|
+
)
|
|
71
|
+
response = await self._map_to_response(updated)
|
|
72
|
+
response.profile_picture_url = await self.file_url(updated.profile_picture)
|
|
73
|
+
return response
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That's it. Against the ~13 hand-written lines, `set_file` resolves the
|
|
77
|
+
entity, calls `replace` (writes the new file **before** deleting the old),
|
|
78
|
+
stores the key and commits — in one step.
|
|
79
|
+
|
|
80
|
+
!!! tip "Safe with the authenticated user"
|
|
81
|
+
`set_file` re-resolves the entity on the request session via
|
|
82
|
+
`repository.resolve()`. If you pass the `UserModel` from
|
|
83
|
+
`get_current_user` (which on mis-wired apps used to be *detached*),
|
|
84
|
+
`resolve` re-attaches it before the write — no
|
|
85
|
+
`InvalidRequestError: Instance is not persistent within this Session`.
|
|
86
|
+
|
|
87
|
+
## Serve the URL — `file_url`
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
url = await self.file_url(user.profile_picture) # 1h lifetime
|
|
91
|
+
url = await self.file_url(user.profile_picture, expires=timedelta(minutes=5))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Returns `None` when the key is empty, so you can feed the result straight into
|
|
95
|
+
a response-schema field without an `if`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
response.profile_picture_url = await self.file_url(updated.profile_picture)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Remove the file — `clear_file`
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
updated = await self.clear_file(user, field="profile_picture")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Deletes the storage object and nulls the field. When the field is already
|
|
108
|
+
empty it is a **no-op**: the entity is returned without a `commit` and without
|
|
109
|
+
touching storage.
|
|
110
|
+
|
|
111
|
+
## Several fields? Same mixin
|
|
112
|
+
|
|
113
|
+
`field=` is just an argument — one service handles as many fields as you like:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
await self.set_file(event, cover, field="cover_image", subdir="events/covers")
|
|
117
|
+
await self.set_file(event, banner, field="banner_image", subdir="events/banners")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Recap
|
|
121
|
+
|
|
122
|
+
- Mix `StoredFileServiceMixin[Model]` into the service and expose
|
|
123
|
+
`upload_utils` + `storage`.
|
|
124
|
+
- `set_file(ref, file, *, field, subdir=...)` → upload, swap old, persist.
|
|
125
|
+
Detach-safe.
|
|
126
|
+
- `file_url(key, *, expires=...)` → presigned URL or `None`.
|
|
127
|
+
- `clear_file(ref, *, field)` → delete + null (no-op when already empty).
|
|
128
|
+
- Common case (one key + presigned). For resize/variants/gallery, use
|
|
129
|
+
[`UploadUtils`](uploads.md) directly.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Arquivo no serviço — `StoredFileServiceMixin`
|
|
2
|
+
|
|
3
|
+
Uma entidade quase sempre carrega **uma** chave de storage: o avatar do
|
|
4
|
+
usuário, o banner de um evento, a capa de um produto, um anexo. E todo
|
|
5
|
+
serviço que cuida disso reescreve a mesma dança na mão:
|
|
6
|
+
|
|
7
|
+
1. resolver a entidade,
|
|
8
|
+
2. subir o arquivo novo e apagar o antigo,
|
|
9
|
+
3. gravar a chave nova no model,
|
|
10
|
+
4. dar `commit`,
|
|
11
|
+
5. devolver uma URL temporária de download.
|
|
12
|
+
|
|
13
|
+
O `StoredFileServiceMixin` faz esse fluxo **uma vez**, parametrizado pelo
|
|
14
|
+
**nome do campo** — então um mesmo serviço cuida de vários campos de arquivo
|
|
15
|
+
sem repetição. Ele monta por cima do [`UploadUtils`](uploads.md) (upload +
|
|
16
|
+
validação) e do [`AsyncMinIOClient`](storage.md) (presigned URL); requer os
|
|
17
|
+
extras `[upload]` e `[minio]`.
|
|
18
|
+
|
|
19
|
+
!!! info "Escopo: o caso comum"
|
|
20
|
+
Cobre **uma chave por campo → URL presigned**. Thumbnails, variantes
|
|
21
|
+
(S/M/L), bucket público/CDN e galerias (um-para-muitos) ficam de fora —
|
|
22
|
+
pra esses, componha o `UploadUtils` direto.
|
|
23
|
+
|
|
24
|
+
## Misturando no seu serviço
|
|
25
|
+
|
|
26
|
+
O mixin não constrói nada: ele lê dois colaboradores de `self` —
|
|
27
|
+
`upload_utils` e `storage`. Quem mistura continua dono da configuração
|
|
28
|
+
(tamanho, tipos aceitos, bucket):
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from tempest_fastapi_sdk import (
|
|
32
|
+
AsyncMinIOClient,
|
|
33
|
+
BaseService,
|
|
34
|
+
StoredFileServiceMixin,
|
|
35
|
+
UploadUtils,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
from src.db.models import UserModel
|
|
39
|
+
from src.db.repositories import UserRepository
|
|
40
|
+
from src.schemas import UserResponseSchema
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class UserService(
|
|
44
|
+
BaseService[UserRepository, UserResponseSchema],
|
|
45
|
+
StoredFileServiceMixin[UserModel],
|
|
46
|
+
):
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
repository: UserRepository,
|
|
50
|
+
storage: AsyncMinIOClient,
|
|
51
|
+
upload_utils: UploadUtils,
|
|
52
|
+
) -> None:
|
|
53
|
+
super().__init__(repository)
|
|
54
|
+
self.storage = storage
|
|
55
|
+
self.upload_utils = upload_utils
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A ordem das bases importa: `BaseService` traz o `repository`; o mixin só
|
|
59
|
+
adiciona os métodos de arquivo por cima.
|
|
60
|
+
|
|
61
|
+
## Trocar o arquivo — `set_file`
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
async def update_profile_picture(
|
|
65
|
+
self, user: UUID | UserModel, image: UploadFile
|
|
66
|
+
) -> UserResponseSchema:
|
|
67
|
+
"""Sobe a foto nova, apaga a antiga e devolve o perfil com a URL."""
|
|
68
|
+
updated = await self.set_file(
|
|
69
|
+
user, image, field="profile_picture", subdir="profiles"
|
|
70
|
+
)
|
|
71
|
+
response = await self._map_to_response(updated)
|
|
72
|
+
response.profile_picture_url = await self.file_url(updated.profile_picture)
|
|
73
|
+
return response
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Foi isso. Comparado às ~13 linhas manuais, o `set_file` resolve a entidade,
|
|
77
|
+
chama `replace` (grava a nova **antes** de apagar a antiga), grava a chave e
|
|
78
|
+
dá `commit` — tudo num passo.
|
|
79
|
+
|
|
80
|
+
!!! tip "Seguro com o usuário autenticado"
|
|
81
|
+
O `set_file` re-resolve a entidade na sessão do request via
|
|
82
|
+
`repository.resolve()`. Se você passar o `UserModel` que veio de
|
|
83
|
+
`get_current_user` (que em apps mal-fiados vinha *detached*), o `resolve`
|
|
84
|
+
o reanexa antes da escrita — sem o
|
|
85
|
+
`InvalidRequestError: Instance is not persistent within this Session`.
|
|
86
|
+
|
|
87
|
+
## Servir a URL — `file_url`
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
url = await self.file_url(user.profile_picture) # 1h de validade
|
|
91
|
+
url = await self.file_url(user.profile_picture, expires=timedelta(minutes=5))
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Devolve `None` quando a chave é vazia, então você pode jogar o resultado
|
|
95
|
+
direto num campo do schema de resposta sem `if`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
response.profile_picture_url = await self.file_url(updated.profile_picture)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Remover o arquivo — `clear_file`
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
updated = await self.clear_file(user, field="profile_picture")
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Apaga o objeto do storage e zera o campo. Quando o campo já está vazio, é
|
|
108
|
+
um **no-op**: a entidade volta sem `commit` e sem chamar o storage.
|
|
109
|
+
|
|
110
|
+
## Vários campos? Mesmo mixin
|
|
111
|
+
|
|
112
|
+
`field=` é só um argumento — o mesmo serviço cuida de quantos campos quiser:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
await self.set_file(event, cover, field="cover_image", subdir="events/covers")
|
|
116
|
+
await self.set_file(event, banner, field="banner_image", subdir="events/banners")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Recapitulando
|
|
120
|
+
|
|
121
|
+
- Misture `StoredFileServiceMixin[Model]` no serviço e exponha `upload_utils`
|
|
122
|
+
+ `storage`.
|
|
123
|
+
- `set_file(ref, file, *, field, subdir=...)` → sobe, troca a antiga,
|
|
124
|
+
persiste. Detach-safe.
|
|
125
|
+
- `file_url(key, *, expires=...)` → URL presigned ou `None`.
|
|
126
|
+
- `clear_file(ref, *, field)` → apaga + zera (no-op se já vazio).
|
|
127
|
+
- Caso comum (uma chave + presigned). Pra resize/variantes/galeria, use o
|
|
128
|
+
[`UploadUtils`](uploads.md) direto.
|
|
@@ -68,6 +68,7 @@ Gerada automaticamente a partir das docstrings do SDK via [`mkdocstrings`](https
|
|
|
68
68
|
## Services & Controllers
|
|
69
69
|
|
|
70
70
|
::: tempest_fastapi_sdk.services.base.BaseService
|
|
71
|
+
::: tempest_fastapi_sdk.services.file_mixin.StoredFileServiceMixin
|
|
71
72
|
::: tempest_fastapi_sdk.controllers.base.BaseController
|
|
72
73
|
|
|
73
74
|
---
|
|
@@ -220,6 +220,7 @@ nav:
|
|
|
220
220
|
- "Observabilidade (tracing)": recipes/observability.md
|
|
221
221
|
- Storage (MinIO/S3): recipes/storage.md
|
|
222
222
|
- Uploads (backends): recipes/uploads.md
|
|
223
|
+
- "Arquivo no serviço (mixin)": recipes/stored-files.md
|
|
223
224
|
- Downloads: recipes/downloads.md
|
|
224
225
|
- Idempotência: recipes/idempotency.md
|
|
225
226
|
- Sync offline-first (delta): recipes/offline-sync.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tempest-fastapi-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.64.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"
|
|
@@ -180,7 +180,12 @@ from tempest_fastapi_sdk.schemas import (
|
|
|
180
180
|
decode_cursor,
|
|
181
181
|
encode_cursor,
|
|
182
182
|
)
|
|
183
|
-
from tempest_fastapi_sdk.services import
|
|
183
|
+
from tempest_fastapi_sdk.services import (
|
|
184
|
+
BaseService,
|
|
185
|
+
StoredFileServiceMixin,
|
|
186
|
+
SupportsPresign,
|
|
187
|
+
SupportsUpload,
|
|
188
|
+
)
|
|
184
189
|
from tempest_fastapi_sdk.sessions import (
|
|
185
190
|
MemorySessionStore,
|
|
186
191
|
RedisSessionStore,
|
|
@@ -301,7 +306,7 @@ from tempest_fastapi_sdk.websockets import (
|
|
|
301
306
|
make_websocket_router,
|
|
302
307
|
)
|
|
303
308
|
|
|
304
|
-
__version__: str = "0.
|
|
309
|
+
__version__: str = "0.64.0"
|
|
305
310
|
|
|
306
311
|
__all__: list[str] = [
|
|
307
312
|
"BASE_COLUMN_ORDER",
|
|
@@ -462,6 +467,9 @@ __all__: list[str] = [
|
|
|
462
467
|
"SlowQueryLogger",
|
|
463
468
|
"SoftDeleteMixin",
|
|
464
469
|
"StateBR",
|
|
470
|
+
"StoredFileServiceMixin",
|
|
471
|
+
"SupportsPresign",
|
|
472
|
+
"SupportsUpload",
|
|
465
473
|
"SyncFilterSchema",
|
|
466
474
|
"SyncPaginationSchema",
|
|
467
475
|
"SystemMetrics",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Business-logic layer base classes."""
|
|
2
|
+
|
|
3
|
+
from tempest_fastapi_sdk.services.base import BaseService
|
|
4
|
+
from tempest_fastapi_sdk.services.file_mixin import (
|
|
5
|
+
StoredFileServiceMixin,
|
|
6
|
+
SupportsPresign,
|
|
7
|
+
SupportsUpload,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__: list[str] = [
|
|
11
|
+
"BaseService",
|
|
12
|
+
"StoredFileServiceMixin",
|
|
13
|
+
"SupportsPresign",
|
|
14
|
+
"SupportsUpload",
|
|
15
|
+
]
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Reusable service mixin for single-key stored-file fields.
|
|
2
|
+
|
|
3
|
+
A great many entities carry exactly one object-storage key — an avatar,
|
|
4
|
+
a banner, a cover, a document — and every service that owns one rewrites
|
|
5
|
+
the same orchestration by hand:
|
|
6
|
+
|
|
7
|
+
1. resolve the entity (detach-safe),
|
|
8
|
+
2. upload the new file and delete the one it replaces,
|
|
9
|
+
3. write the new key back onto the model,
|
|
10
|
+
4. commit, and
|
|
11
|
+
5. hand out a presigned download URL.
|
|
12
|
+
|
|
13
|
+
:class:`StoredFileServiceMixin` encodes that flow once, parameterized by
|
|
14
|
+
the **field name**, so a single service can manage several file fields
|
|
15
|
+
without duplication. It deliberately covers only the common
|
|
16
|
+
"one key column → presigned URL" case; resize/thumbnail pipelines,
|
|
17
|
+
multi-variant assets and gallery (one-to-many) uploads are out of scope —
|
|
18
|
+
compose :class:`UploadUtils` directly for those.
|
|
19
|
+
|
|
20
|
+
The mixin is storage-agnostic by structural typing: it reads two
|
|
21
|
+
collaborators off ``self`` — ``upload_utils`` and ``storage`` — described
|
|
22
|
+
by the :class:`SupportsUpload` and :class:`SupportsPresign` protocols, so
|
|
23
|
+
importing it never pulls the optional ``[upload]`` / ``[minio]`` extras.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from datetime import timedelta
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import TYPE_CHECKING, Any, Generic, Protocol, TypeVar
|
|
31
|
+
from uuid import UUID
|
|
32
|
+
|
|
33
|
+
from tempest_fastapi_sdk.db.model import BaseModel
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from fastapi import UploadFile
|
|
37
|
+
|
|
38
|
+
from tempest_fastapi_sdk.db.repository import BaseRepository
|
|
39
|
+
|
|
40
|
+
ModelType = TypeVar("ModelType", bound=BaseModel)
|
|
41
|
+
|
|
42
|
+
# One hour matches AsyncMinIOClient.presigned_get_url's own default.
|
|
43
|
+
_DEFAULT_URL_TTL = timedelta(hours=1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class SupportsUpload(Protocol):
|
|
47
|
+
"""Structural type for the upload helper the mixin needs.
|
|
48
|
+
|
|
49
|
+
Satisfied by :class:`tempest_fastapi_sdk.UploadUtils`.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
async def replace(
|
|
53
|
+
self,
|
|
54
|
+
old_key: Path | str | None,
|
|
55
|
+
file: UploadFile,
|
|
56
|
+
*,
|
|
57
|
+
subdir: str = ...,
|
|
58
|
+
filename: str | None = ...,
|
|
59
|
+
keep_original_name: bool = ...,
|
|
60
|
+
) -> Path:
|
|
61
|
+
"""Persist ``file`` and delete the object at ``old_key``."""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
async def delete(self, key: Path | str) -> bool:
|
|
65
|
+
"""Delete the object at ``key``; return whether it existed."""
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class SupportsPresign(Protocol):
|
|
70
|
+
"""Structural type for the storage client the mixin needs.
|
|
71
|
+
|
|
72
|
+
Satisfied by :class:`tempest_fastapi_sdk.AsyncMinIOClient`.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
async def presigned_get_url(
|
|
76
|
+
self,
|
|
77
|
+
key: str,
|
|
78
|
+
*,
|
|
79
|
+
expires: timedelta = ...,
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Return a temporary download URL for ``key``."""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class StoredFileServiceMixin(Generic[ModelType]):
|
|
86
|
+
"""Manage a single object-storage key field on a service's model.
|
|
87
|
+
|
|
88
|
+
Mix into a service that already owns a ``repository`` (e.g. a
|
|
89
|
+
:class:`tempest_fastapi_sdk.BaseService` subclass) and exposes an
|
|
90
|
+
``upload_utils`` and a ``storage`` collaborator::
|
|
91
|
+
|
|
92
|
+
class UserService(
|
|
93
|
+
BaseService[UserRepository, UserResponseSchema],
|
|
94
|
+
StoredFileServiceMixin[UserModel],
|
|
95
|
+
):
|
|
96
|
+
def __init__(self, repository, storage, upload_utils):
|
|
97
|
+
super().__init__(repository)
|
|
98
|
+
self.storage = storage
|
|
99
|
+
self.upload_utils = upload_utils
|
|
100
|
+
|
|
101
|
+
user = await service.set_file(
|
|
102
|
+
user, image, field="profile_picture", subdir="profiles"
|
|
103
|
+
)
|
|
104
|
+
url = await service.file_url(user.profile_picture)
|
|
105
|
+
|
|
106
|
+
The mixin does not construct its collaborators — it reads them off
|
|
107
|
+
``self`` so the owning service stays in charge of configuration
|
|
108
|
+
(size limits, allowed types, bucket).
|
|
109
|
+
|
|
110
|
+
Generic parameters:
|
|
111
|
+
ModelType: The ORM model whose field is managed.
|
|
112
|
+
|
|
113
|
+
Attributes:
|
|
114
|
+
repository (BaseRepository[ModelType]): Supplied by the service.
|
|
115
|
+
upload_utils (SupportsUpload): Supplied by the service.
|
|
116
|
+
storage (SupportsPresign): Supplied by the service.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
repository: BaseRepository[ModelType]
|
|
120
|
+
upload_utils: SupportsUpload
|
|
121
|
+
storage: SupportsPresign
|
|
122
|
+
|
|
123
|
+
async def set_file(
|
|
124
|
+
self,
|
|
125
|
+
ref: UUID | ModelType,
|
|
126
|
+
file: UploadFile,
|
|
127
|
+
*,
|
|
128
|
+
field: str,
|
|
129
|
+
subdir: str = "",
|
|
130
|
+
filename: str | None = None,
|
|
131
|
+
keep_original_name: bool = False,
|
|
132
|
+
) -> ModelType:
|
|
133
|
+
"""Upload ``file`` and store its key on ``field``, replacing any old one.
|
|
134
|
+
|
|
135
|
+
The new object is written **before** the old one is deleted (via
|
|
136
|
+
:meth:`SupportsUpload.replace`), so a failed upload leaves the
|
|
137
|
+
existing file untouched. The entity is re-resolved on the
|
|
138
|
+
repository's session first, so a detached instance (e.g. the
|
|
139
|
+
authenticated user) is safely re-attached before the write.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
ref (UUID | ModelType): The entity to update, by id or instance.
|
|
143
|
+
file (UploadFile): The uploaded file.
|
|
144
|
+
field (str): Name of the model attribute holding the storage key.
|
|
145
|
+
subdir (str): Optional key prefix / sub-directory.
|
|
146
|
+
filename (str | None): Explicit final filename. When omitted the
|
|
147
|
+
upload helper derives one.
|
|
148
|
+
keep_original_name (bool): Preserve the upload's original
|
|
149
|
+
filename when ``filename`` is not given.
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
ModelType: The persisted entity with the new key on ``field``.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
AppException: ``repository.not_found_exception`` when ``ref`` is
|
|
156
|
+
an id with no matching row.
|
|
157
|
+
"""
|
|
158
|
+
entity = await self.repository.resolve(ref)
|
|
159
|
+
old_key: Any = getattr(entity, field)
|
|
160
|
+
new_key = await self.upload_utils.replace(
|
|
161
|
+
old_key,
|
|
162
|
+
file,
|
|
163
|
+
subdir=subdir,
|
|
164
|
+
filename=filename,
|
|
165
|
+
keep_original_name=keep_original_name,
|
|
166
|
+
)
|
|
167
|
+
setattr(entity, field, str(new_key))
|
|
168
|
+
return await self.repository.update(entity)
|
|
169
|
+
|
|
170
|
+
async def clear_file(
|
|
171
|
+
self,
|
|
172
|
+
ref: UUID | ModelType,
|
|
173
|
+
*,
|
|
174
|
+
field: str,
|
|
175
|
+
) -> ModelType:
|
|
176
|
+
"""Delete the stored object (if any) and null ``field``.
|
|
177
|
+
|
|
178
|
+
A no-op write is avoided when the field is already empty: the
|
|
179
|
+
resolved entity is returned without a commit.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
ref (UUID | ModelType): The entity to update, by id or instance.
|
|
183
|
+
field (str): Name of the model attribute holding the storage key.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
ModelType: The entity with ``field`` set to ``None`` (persisted
|
|
187
|
+
only when there was a key to clear).
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
AppException: ``repository.not_found_exception`` when ``ref`` is
|
|
191
|
+
an id with no matching row.
|
|
192
|
+
"""
|
|
193
|
+
entity = await self.repository.resolve(ref)
|
|
194
|
+
old_key: Any = getattr(entity, field)
|
|
195
|
+
if not old_key:
|
|
196
|
+
return entity
|
|
197
|
+
await self.upload_utils.delete(old_key)
|
|
198
|
+
setattr(entity, field, None)
|
|
199
|
+
return await self.repository.update(entity)
|
|
200
|
+
|
|
201
|
+
async def file_url(
|
|
202
|
+
self,
|
|
203
|
+
key: str | None,
|
|
204
|
+
*,
|
|
205
|
+
expires: timedelta = _DEFAULT_URL_TTL,
|
|
206
|
+
) -> str | None:
|
|
207
|
+
"""Return a presigned download URL for ``key``, or ``None`` when empty.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
key (str | None): The stored object key (typically the field
|
|
211
|
+
value). ``None`` / empty returns ``None`` so callers can
|
|
212
|
+
forward the result straight into a response schema.
|
|
213
|
+
expires (timedelta): URL lifetime. Defaults to one hour.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
str | None: A temporary download URL, or ``None`` when there is
|
|
217
|
+
no key.
|
|
218
|
+
"""
|
|
219
|
+
if not key:
|
|
220
|
+
return None
|
|
221
|
+
return await self.storage.presigned_get_url(key, expires=expires)
|