Referência da API¶
Gerada automaticamente a partir das docstrings do SDK via mkdocstrings. Todo símbolo público exportado por tempest_fastapi_sdk está documentado aqui com sua assinatura completa, parâmetros, tipo de retorno, exceções levantadas e localização no código-fonte.
Buscando
Use a barra de busca no topo da página (ou pressione /) para pular para um símbolo pelo nome. O índice full-text inclui as docstrings, então buscas como "soft delete" ou "request id" caem na classe certa.
Superfície de topo¶
tempest_fastapi_sdk
¶
tempest-fastapi-sdk — shared FastAPI/SQLAlchemy/Pydantic primitives.
FieldRef
module-attribute
¶
A column reference: either a mapped attribute (Model.email) or its
string key ("email"). Column references give editor autocomplete and
typo-checking; strings remain accepted for dynamic configuration.
OrderRef
module-attribute
¶
An ordering reference: a column (Model.created_at, ascending), a
direction-wrapped column (desc(Model.created_at)), or a Django-style
string ("created_at" / "-created_at").
CSRF_COOKIE_NAME
module-attribute
¶
Default cookie holding the CSRF token.
CSRF_HEADER_NAME
module-attribute
¶
Default header the client echoes the cookie value into.
HealthCheck
module-attribute
¶
Type alias for async health-check callables.
Each callable returns True when the dependency is healthy and
False (or raises) otherwise. Exceptions are caught by the
readiness endpoint and translated to False.
LogSource
module-attribute
¶
Selectable log source for the /logs endpoint.
"all" merges every per-level file (excluding 500.log to avoid
duplicating error.log rows); the rest map to a single file.
BASE_COLUMN_ORDER
module-attribute
¶
Columns inherited from BaseModel — emitted in this exact order at
the top of every op.create_table produced by autogenerate.
NAMING_CONVENTION
module-attribute
¶
NAMING_CONVENTION: dict[str, str] = {
"ix": "ix_%(column_0_label)s",
"uq": "uq_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
"pk": "pk_%(table_name)s",
}
Alembic-friendly naming convention applied to every constraint.
Constraint names become deterministic across machines and DB engines,
so alembic revision --autogenerate only emits real schema diffs
instead of churn from auto-generated identifiers.
CEP
module-attribute
¶
Pydantic type that validates a Brazilian CEP and normalizes to 8 digits.
CEP_PATTERN
module-attribute
¶
Match a Brazilian CEP in either masked (00000-000) or raw form.
CNPJ
module-attribute
¶
Pydantic type that validates and normalizes a CNPJ to 14 digits.
CNPJ_PATTERN
module-attribute
¶
Match a CNPJ in either masked (00.000.000/0000-00) or raw form.
CPF
module-attribute
¶
Pydantic type that validates and normalizes a CPF to 11 digits.
CPF_CNPJ_PATTERN
module-attribute
¶
Match either a CPF or a CNPJ (masked or raw).
CPF_PATTERN
module-attribute
¶
Match a CPF in either masked (000.000.000-00) or raw form.
PHONE_BR_PATTERN
module-attribute
¶
PHONE_BR_PATTERN: Final[Pattern[str]] = compile(
"^(?:\\+?55\\s?)?(?:\\(?\\d{2}\\)?\\s?)?9?\\d{4}[-\\s]?\\d{4}$"
)
Match a BR phone number with optional +55, DDD, mask or 9th digit.
REQUEST_ID_HEADER
module-attribute
¶
Outbound header carrying the inbound correlation id.
CPFOrCNPJ
module-attribute
¶
Pydantic type that accepts either a CPF or a CNPJ.
PhoneBR
module-attribute
¶
Pydantic type that validates a BR phone and normalizes to digits.
AdminAuthBackend
¶
Bases: ABC
Abstract base for admin authentication.
Implementations receive a session-bound async DB session per
login attempt; the default :class:UserModelAuthBackend queries
a :class:BaseUserModel subclass and enforces is_admin=True.
Custom backends can use the same protocol to integrate LDAP,
OAuth, IAM tokens, etc.
AdminAuthError
¶
Bases: Exception
Raised by authentication backends when credentials are rejected.
Captures the user-facing message the login template should render alongside the HTTP status code (default 401). Specific failure reasons should map to subclasses of this base exception for granular templating.
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The end-user-facing message. |
'Invalid credentials'
|
status_code
|
int
|
HTTP status code to attach. |
401
|
Source code in tempest_fastapi_sdk/admin/auth.py
AdminModel
¶
AdminModel(
model: type[ModelT],
*,
list_display: Sequence[FieldRef] | None = None,
list_filter: Sequence[FieldRef] = (),
search_fields: Sequence[FieldRef] = (),
readonly_fields: Sequence[FieldRef] = (),
ordering: OrderRef | None = None,
page_size: int = 25,
identity_field: FieldRef = "id",
repository_class: type[BaseRepository[Any]] | None = None,
verbose_name: str | None = None,
verbose_name_plural: str | None = None,
)
Bases: Generic[ModelT]
Declarative admin configuration for one SQLAlchemy model.
Instantiate once per managed model and pass it to
:meth:AdminSite.register. Unlike Django's class-based ModelAdmin,
this is a plain typed instance — the constructor signature is the
contract, fields accept real SQLAlchemy column attributes (so typos
surface in the editor, not at runtime), and there is no metaclass
magic::
site.register(AdminModel(
model=UserModel,
list_display=[UserModel.email, UserModel.is_admin],
search_fields=[UserModel.email],
ordering=desc(UserModel.created_at),
))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
type[ModelT]
|
The SQLAlchemy model class. |
required |
list_display
|
Sequence[FieldRef] | None
|
Columns shown in the
list view. |
None
|
list_filter
|
Sequence[FieldRef]
|
Fields surfaced as filter dropdowns; matched via the repository's standard filter pipeline. |
()
|
search_fields
|
Sequence[FieldRef]
|
String columns searched with
|
()
|
readonly_fields
|
Sequence[FieldRef]
|
Fields locked in the detail view. |
()
|
ordering
|
OrderRef | None
|
Default ordering. Accepts a column
(ascending), |
None
|
page_size
|
int
|
Default rows per page in the list view. |
25
|
identity_field
|
FieldRef
|
Column used to look up a single row
from the detail URL. Defaults to |
'id'
|
repository_class
|
type[BaseRepository[Any]] | None
|
Concrete
repository. |
None
|
verbose_name
|
str | None
|
Singular display name; defaults to the model name humanized. |
None
|
verbose_name_plural
|
str | None
|
Plural display name; defaults to
|
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
Build and validate the configuration. See class docstring.
Source code in tempest_fastapi_sdk/admin/config.py
AdminSite
¶
AdminSite(
title: str = "Admin",
*,
index_subtitle: str = "Site administration",
site_url: str | None = None,
)
Holds the set of :class:AdminModel configurations to expose.
Each project instantiates one site, registers its admin
configurations, and passes the site to :func:make_admin_router.
Sites are explicit (no auto-discovery) so the surface remains
predictable across deployments.
Attributes:
| Name | Type | Description |
|---|---|---|
title |
str
|
Branding shown at the top of every admin page. |
index_subtitle |
str
|
Optional subtitle for the dashboard. |
site_url |
str | None
|
Optional "View site" link rendered in the admin header. |
Initialize the site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
Branding text. |
'Admin'
|
index_subtitle
|
str
|
Dashboard subtitle. |
'Site administration'
|
site_url
|
str | None
|
Optional outbound link rendered in the admin header. |
None
|
Source code in tempest_fastapi_sdk/admin/site.py
UserModelAuthBackend
¶
UserModelAuthBackend(user_model: type[BaseUserModel])
Bases: AdminAuthBackend
Default backend backed by :class:BaseUserModel.
Authenticates by selecting the row whose email matches the
inbound identifier (case-insensitive), verifying the password via
:class:tempest_fastapi_sdk.PasswordUtils and enforcing both
is_admin=True and is_active=True. The
:attr:last_login_at column is stamped on every successful login.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
The concrete model class.
Must be a subclass of :class: |
required |
Initialize the backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
The user model to query. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
Source code in tempest_fastapi_sdk/admin/auth.py
BodySizeLimitMiddleware
¶
Pure ASGI middleware enforcing max_bytes per request.
Two checks happen:
- Header check —
Content-Lengthgreater than the cap short-circuits immediately with a413response. This catches the common case where the client knows the size. - Streaming check — for chunked / unknown-length uploads
the middleware tracks bytes seen in the
http.requestmessages and aborts once the cap is crossed.
Excluded paths bypass the check entirely (typical use: an upload endpoint that intentionally accepts larger bodies and enforces its own per-route limit).
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI app. |
required |
max_bytes
|
int
|
Hard cap on the request body in bytes.
|
required |
exclude_paths
|
tuple[str, ...]
|
Path prefixes that
bypass the limit. Match is |
()
|
Source code in tempest_fastapi_sdk/api/middlewares/body_size.py
CachedResponse
dataclass
¶
CachedResponse(
status_code: int,
headers: list[tuple[str, str]],
body: bytes,
media_type: str | None,
)
Serialized response stored under an idempotency key.
Attributes:
| Name | Type | Description |
|---|---|---|
status_code |
int
|
HTTP status of the original response. |
headers |
list[tuple[str, str]]
|
Response headers as a flat
list of |
body |
bytes
|
Raw response body bytes. |
media_type |
str | None
|
Original |
CSRFMiddleware
¶
CSRFMiddleware(
app: ASGIApp,
*,
cookie_name: str = CSRF_COOKIE_NAME,
header_name: str = CSRF_HEADER_NAME,
exclude_paths: tuple[str, ...] = (),
)
Bases: BaseHTTPMiddleware
Double-submit cookie CSRF guard.
On unsafe methods (POST / PUT / PATCH / DELETE)
the request MUST carry:
- The CSRF cookie (
csrf_tokenby default). - The CSRF header (
X-CSRF-Tokenby default) with the same value.
Missing or mismatched values return 403 with the SDK
envelope. Excluded paths bypass the check — typical use:
/api/ routes that use Authorization: Bearer (not
susceptible to CSRF), or webhook callbacks whose
authentication is signature-based.
Safe methods (GET / HEAD / OPTIONS) always pass.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
Wrapped app. |
required |
cookie_name
|
str
|
Name of the CSRF cookie. |
CSRF_COOKIE_NAME
|
header_name
|
str
|
Name of the CSRF header. |
CSRF_HEADER_NAME
|
exclude_paths
|
tuple[str, ...]
|
Path prefixes that
bypass the check (e.g. |
()
|
Source code in tempest_fastapi_sdk/api/middlewares/csrf.py
GitHubOAuthClient
¶
GitHubOAuthClient(
*,
client_id: str,
client_secret: str,
redirect_uri: str,
scopes: list[str] | None = None,
http_client: HTTPClient | None = None,
)
Bases: _BaseOAuthClient
GitHub OAuth client.
GitHub doesn't issue an id_token — the user identity comes
from GET /user. Default scopes: read:user user:email.
Source code in tempest_fastapi_sdk/api/oauth.py
GoogleOAuthClient
¶
GoogleOAuthClient(
*,
client_id: str,
client_secret: str,
redirect_uri: str,
scopes: list[str] | None = None,
http_client: HTTPClient | None = None,
)
Bases: _BaseOAuthClient
Google identity client (OIDC-compatible).
Default scopes: openid email profile.
Source code in tempest_fastapi_sdk/api/oauth.py
HardenedStaticFiles
¶
HardenedStaticFiles(
*args: object, security_headers: dict[str, str] | None = None, **kwargs: object
)
Bases: StaticFiles
StaticFiles that stamps anti-XSS headers on every response.
Defense in depth for serving user-uploaded content: if a malicious file ever lands on disk (an upload-validation bypass, a manual operator action), serving it does not become a stored-XSS primitive against any same-origin SPA.
Use exactly like :class:starlette.staticfiles.StaticFiles —
app.mount("/uploads", HardenedStaticFiles(directory=...)). Pass
security_headers= to override or extend the defaults
(:data:DEFAULT_STATIC_SECURITY_HEADERS). Existing headers set by
the parent are preserved (setdefault semantics).
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*args
|
object
|
Positional arguments forwarded to |
()
|
security_headers
|
dict[str, str] | None
|
Headers to stamp
on every response. Defaults to
:data: |
None
|
**kwargs
|
object
|
Keyword arguments forwarded to |
{}
|
Source code in tempest_fastapi_sdk/api/static.py
IdempotencyMiddleware
¶
IdempotencyMiddleware(
app: ASGIApp,
*,
store: IdempotencyStore,
ttl_seconds: int = 24 * 3600,
header_name: str = IDEMPOTENCY_HEADER,
)
Bases: BaseHTTPMiddleware
ASGI middleware caching responses by Idempotency-Key.
Only mutating verbs (POST / PUT / PATCH /
DELETE) are eligible. The key is scoped per
(method, path, key) so a key reused across different
endpoints doesn't collide.
Add to FastAPI like any other ASGI middleware:
from tempest_fastapi_sdk import (
IdempotencyMiddleware,
MemoryIdempotencyStore,
)
app.add_middleware(
IdempotencyMiddleware,
store=MemoryIdempotencyStore(),
ttl_seconds=24 * 3600,
)
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI app. |
required |
store
|
IdempotencyStore
|
Backend used to cache responses.
Pass :class: |
required |
ttl_seconds
|
int
|
How long to keep cached responses. Stripe defaults to 24 hours — long enough to cover client retries with exponential backoff. |
24 * 3600
|
header_name
|
str
|
Header carrying the idempotency key.
Defaults to the canonical |
IDEMPOTENCY_HEADER
|
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
IdempotencyStore
¶
Bases: Protocol
Protocol every idempotency cache implements.
MemoryIdempotencyStore
¶
In-process :class:IdempotencyStore with TTL eviction.
Single-replica only — a second replica won't see entries stored by the first. Suitable for dev, tests, and small services that haven't scaled out yet.
The eviction is best-effort: TTLs are checked on access; no background thread cleans the dict. Memory grows linearly with cached requests until they expire, so set a sensible TTL.
Initialize the in-memory store.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
OAuthError
¶
OAuthError(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when an OAuth exchange fails — wraps the IdP message.
Source code in tempest_fastapi_sdk/exceptions/base.py
OAuthTokens
dataclass
¶
OAuthTokens(
access_token: str,
token_type: str,
refresh_token: str | None = None,
id_token: str | None = None,
expires_in: int | None = None,
scope: str | None = None,
raw: dict[str, Any] = dict(),
)
Tokens returned by the IdP after the authorization-code exchange.
Attributes:
| Name | Type | Description |
|---|---|---|
access_token |
str
|
Bearer token to call provider APIs. |
token_type |
str
|
Usually |
refresh_token |
str | None
|
Refresh token when offline access was requested. |
id_token |
str | None
|
OIDC id token (JWT). Present on OIDC flows, absent on plain OAuth2. |
expires_in |
int | None
|
Lifetime of |
scope |
str | None
|
Space-separated scopes granted. |
raw |
dict[str, Any]
|
Full token-endpoint response. |
OAuthUser
dataclass
¶
OAuthUser(
provider: str,
subject: str,
email: str | None = None,
name: str | None = None,
picture: str | None = None,
raw: dict[str, Any] = dict(),
)
Normalized user identity returned by every provider.
Different IdPs use different field names (sub vs id,
picture vs avatar_url, name vs login). This
dataclass is the single shape the rest of the application sees.
Attributes:
| Name | Type | Description |
|---|---|---|
provider |
str
|
Provider key ( |
subject |
str
|
Stable per-provider user id. Combine with
|
email |
str | None
|
Verified email when the provider returned one. Some IdPs gate this behind extra scopes. |
name |
str | None
|
Human-readable display name. |
picture |
str | None
|
Avatar / profile picture URL. |
raw |
dict[str, Any]
|
Full provider payload for advanced cases (custom claims, role mappings). |
OIDCProvider
¶
OIDCProvider(
*,
client_id: str,
client_secret: str,
redirect_uri: str,
authorize_url: str,
token_url: str,
userinfo_url: str | None = None,
provider_name: str = "oidc",
scopes: list[str] | None = None,
http_client: HTTPClient | None = None,
)
Bases: _BaseOAuthClient
Generic OIDC provider — works with any conformant IdP.
Pass the authorize / token / userinfo endpoints explicitly,
or fetch them once at boot from the IdP's discovery document
at ${issuer}/.well-known/openid-configuration and pass the
URLs in. Default scopes: openid email profile.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client_id
|
str
|
App client id at the IdP. |
required |
client_secret
|
str
|
App client secret. |
required |
redirect_uri
|
str
|
Registered callback URL. |
required |
authorize_url
|
str
|
IdP's authorize endpoint. |
required |
token_url
|
str
|
IdP's token endpoint. |
required |
userinfo_url
|
str | None
|
IdP's userinfo endpoint.
|
None
|
provider_name
|
str
|
Key embedded in
:attr: |
'oidc'
|
scopes
|
list[str] | None
|
Scopes to request. |
None
|
http_client
|
HTTPClient | None
|
Shared client. |
None
|
Source code in tempest_fastapi_sdk/api/oauth.py
PrometheusMiddleware
¶
PrometheusMiddleware(
app: ASGIApp,
*,
registry: CollectorRegistry,
latency_buckets: tuple[float, ...] = DEFAULT_LATENCY_BUCKETS,
)
Bases: BaseHTTPMiddleware
ASGI middleware tracking HTTP requests on three core metrics.
Registered series:
http_requests_total{method, path, status}(Counter) — every request counts here once the response status is known.http_request_duration_seconds{method, path}(Histogram) — end-to-end latency in seconds.http_requests_in_progress{method}(Gauge) — live inflight count, decremented in afinallyso dropped connections never leave stale gauges.
The path label uses the route template (e.g.
/orders/{order_id}) when the request hit a FastAPI route,
not the raw URL — that keeps the cardinality bounded.
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI app. |
required |
registry
|
CollectorRegistry
|
Shared registry. Reuse the
same instance for |
required |
latency_buckets
|
tuple[float, ...]
|
Histogram bucket upper bounds in seconds. |
DEFAULT_LATENCY_BUCKETS
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/api/routers/metrics.py
RateLimitMiddleware
¶
RateLimitMiddleware(
app: ASGIApp,
*,
max_requests: int = 60,
window_seconds: float = 60.0,
key_func: Callable[[Request], str] | None = None,
trusted_ip_header: str | None = None,
exempt_paths: tuple[str, ...] = (),
retry_after_header: bool = True,
error_message: str = "Too many requests",
)
Bases: BaseHTTPMiddleware
Lightweight in-process sliding-window rate limiter.
Each unique key (by default the client IP) is allowed at most
max_requests requests inside every window_seconds window.
Excess requests are rejected with 429 Too Many Requests and a
Retry-After header. State is held in-process — for multi-worker
deployments, share state via a Redis-backed limiter outside the
SDK or run the limiter behind a single reverse-proxy worker.
Running behind a proxy: the default key is the direct transport
peer, which is the proxy IP once a reverse proxy fronts the app —
so every client collapses into one bucket. Pass
trusted_ip_header (e.g. "x-real-ip") naming a header your
edge sets from its own connection (never a client-supplied
X-Forwarded-For, which is spoofable) so the limit is per real
client. See :func:tempest_fastapi_sdk.utils.get_client_ip.
Attributes:
| Name | Type | Description |
|---|---|---|
max_requests |
int
|
Maximum requests inside the window. |
window_seconds |
float
|
Length of the sliding window. |
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The underlying ASGI app. |
required |
max_requests
|
int
|
Maximum requests per window. |
60
|
window_seconds
|
float
|
Window length in seconds. |
60.0
|
key_func
|
Callable[[Request], str] | None
|
Build a
rate-limit key from the request. Overrides
|
None
|
trusted_ip_header
|
str | None
|
When set (and |
None
|
exempt_paths
|
tuple[str, ...]
|
Paths to skip entirely
(e.g. |
()
|
retry_after_header
|
bool
|
Whether to add a
|
True
|
error_message
|
str
|
Body of the 429 response. |
'Too many requests'
|
Source code in tempest_fastapi_sdk/api/middlewares/rate_limit.py
RedisIdempotencyStore
¶
:class:IdempotencyStore backed by an async redis client.
The cached payload is encoded as JSON so the schema stays
portable across SDK versions: {"status_code", "headers",
"body_b64", "media_type"} with the body base64-encoded
because Redis values are bytes.
Use this in production / multi-replica deployments. Requires
the [cache] extra so the redis async client is
available.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
_RedisLike
|
Async Redis-like client exposing
|
required |
prefix
|
str
|
Key prefix so idempotency entries don't collide with other cached data. |
'idem:'
|
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
RequestIDMiddleware
¶
Bases: BaseHTTPMiddleware
Bind an X-Request-ID header to the request-scoped context.
Reads the inbound header (or generates a fresh UUID v4 when
absent), stores it via :func:set_request_id so log records
written during the request carry the request_id field, and
echoes the same value back on the response so callers can trace
end-to-end across services.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI application. |
required |
header_name
|
str
|
The header to read/write. Defaults to
|
'X-Request-ID'
|
Source code in tempest_fastapi_sdk/api/middlewares/request_id.py
RSAWebhookSignatureVerifier
¶
RSAWebhookSignatureVerifier(
public_key_pem: str | bytes,
*,
algorithm: str = "sha256",
header_name: str = "x-webhook-signature",
)
Validate RSA-signed webhook payloads (OpenPix/Woovi-style).
Some providers sign each webhook with their PRIVATE key and publish
a well-known PUBLIC key; the receiver verifies the asymmetric
signature over the raw body. This complements
:class:WebhookSignatureVerifier (symmetric HMAC) for those
gateways. Uses RSASSA-PKCS1-v1_5 over a configurable hash.
Requires cryptography (ships with the [webpush] extra, or
pip install cryptography); the import is deferred to first use.
Attributes:
| Name | Type | Description |
|---|---|---|
public_key_pem |
bytes
|
The PEM-encoded provider public key. |
algorithm |
str
|
Hash algorithm name ( |
header_name |
str
|
Header carrying the base64 signature. |
Initialize the verifier.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
public_key_pem
|
str | bytes
|
PEM-encoded RSA public key. Strings are encoded as UTF-8. |
required |
algorithm
|
str
|
Hash algorithm — |
'sha256'
|
header_name
|
str
|
Header carrying the base64 signature. |
'x-webhook-signature'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in tempest_fastapi_sdk/api/webhooks.py
WebhookSignatureVerifier
¶
WebhookSignatureVerifier(
secret: str | bytes,
*,
algorithm: str = "sha256",
header_name: str = "X-Signature",
encoding: str = "hex",
prefix: str = "",
)
Validate HMAC-signed webhook payloads (Stripe/GitHub-style).
Providers usually compute hmac(secret, body) with a fixed
algorithm and ship the digest in a request header (hex or base64).
This helper centralizes the verification using
:func:hmac.compare_digest (constant time) and exposes a FastAPI
dependency that reads the raw body without consuming it for the
route handler.
Attributes:
| Name | Type | Description |
|---|---|---|
secret |
bytes
|
The shared secret as bytes. |
algorithm |
str
|
The hashlib algorithm name. |
header_name |
str
|
The request header carrying the signature. |
encoding |
str
|
How the signature is encoded — |
prefix |
str
|
Optional fixed prefix (e.g. |
Initialize the verifier.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
secret
|
str | bytes
|
The shared secret. Strings are encoded as UTF-8. |
required |
algorithm
|
str
|
hashlib algorithm name ( |
'sha256'
|
header_name
|
str
|
Header carrying the signature. |
'X-Signature'
|
encoding
|
str
|
|
'hex'
|
prefix
|
str
|
Optional fixed prefix on the header value
(e.g. |
''
|
Source code in tempest_fastapi_sdk/api/webhooks.py
ActivationResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/activate/{token}.
Returned after the SDK has consumed a one-shot activation
token and flipped the user's is_active=True. The user is
automatically logged in — both JWTs are issued so the
front-end can complete the post-confirmation redirect in one
round-trip.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the freshly-activated user. |
access_token |
str
|
Short-lived JWT. |
refresh_token |
str
|
Long-lived JWT. |
ActivationToken
¶
Bases: BaseSchema
Service-level result of issuing an account-activation token.
Returned by :meth:UserAuthService.signup when activation is
required — i.e. when AUTH_AUTO_ACTIVATE is false. The
plaintext token is included here exactly once; only its
SHA-256 hash is persisted, so this value cannot be recovered
later. Use it to mail the activation link, log it during
tests, or hand it back to the client in dev mode.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the user the token authorizes. |
token |
str
|
Plaintext token — show once, never store. |
url |
str
|
Front-end activation URL with the token
already substituted into |
expires_at |
datetime
|
UTC timestamp the token becomes invalid (default 7 days after issuance). |
LoginResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/login and the password-reset confirm.
Issued only when credentials validate; the bundled router
reuses this shape for both POST /auth/login and
POST /auth/password-reset/confirm since both flows end
with an authenticated session.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the authenticated user. |
access_token |
str
|
Short-lived JWT. |
refresh_token |
str
|
Long-lived JWT. |
LoginSchema
¶
Bases: BaseSchema
Request body for POST /auth/login.
Standard email + password authentication. Both error paths
(wrong password / unknown email / inactive user) collapse
into the same generic UnauthorizedException so attackers
can't enumerate accounts by reading the response.
Attributes:
| Name | Type | Description |
|---|---|---|
email |
EmailStr
|
Login identifier. |
password |
str
|
Plaintext password — verified against the bcrypt hash stored on the row. |
PasswordResetConfirmSchema
¶
Bases: BaseSchema
Request body for POST /auth/password-reset/confirm.
Carries the opaque token the user copied from the reset
link plus the replacement password. The service consumes the
token (one-shot — used_at is stamped) and replaces the
bcrypt hash atomically.
Attributes:
| Name | Type | Description |
|---|---|---|
token |
str
|
Opaque token issued by |
new_password |
str
|
Plaintext replacement password. Length floor is enforced both schema-side and inside the service. |
PasswordResetRequestSchema
¶
Bases: BaseSchema
Request body for POST /auth/password-reset/request.
The endpoint always returns 202 with a generic message —
even when the email isn't on file — so probing the endpoint
can't enumerate accounts. The reset link travels via email
(production) or in the response body when
AUTH_RETURN_TOKEN_IN_RESPONSE=True (dev).
Attributes:
| Name | Type | Description |
|---|---|---|
email |
EmailStr
|
Email of the account asking for a reset. |
PasswordResetResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/password-reset/request.
message is the same generic string regardless of whether
the email matched an account. reset_url is populated only
when AUTH_RETURN_TOKEN_IN_RESPONSE=True or when the
[email] extra isn't installed — otherwise the link only
travels through SMTP.
Attributes:
| Name | Type | Description |
|---|---|---|
message |
str
|
Human-readable summary of the next step. Always identical across the "email found" / "email not found" branches. |
reset_url |
str | None
|
Front-end reset URL when the
caller asked for an inline response, |
PasswordResetToken
¶
Bases: BaseSchema
Service-level result of issuing a password-reset token.
Returned by :meth:UserAuthService.request_password_reset
when the email matches a user and the caller asked the
service to surface the link (either via
AUTH_RETURN_TOKEN_IN_RESPONSE=True or because no
:class:EmailUtils was wired). The plaintext token is
one-shot, hashed at rest, and expires after
AUTH_PASSWORD_RESET_TTL_SECONDS (default 1 hour).
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the user whose password the token authorizes resetting. |
token |
str
|
Plaintext token — display once, never store. |
url |
str
|
Front-end reset URL with the token already
substituted into |
expires_at |
datetime
|
UTC timestamp the token becomes invalid. |
SignupResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/signup.
The shape depends on the active settings:
- When
AUTH_AUTO_ACTIVATE=Truethe user is born active,activation_required=Falseand bothaccess_token/refresh_tokenare populated — the client can log in immediately. - When
AUTH_AUTO_ACTIVATE=False(production default) the user must confirm the activation link before logging in.activation_required=True, the tokens stayNoneandactivation_urlis set only whenAUTH_RETURN_TOKEN_IN_RESPONSE=True(dev) or when the[email]extra isn't wired (so the link has to ship via the response instead of via SMTP).
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
Primary key of the freshly-inserted row. |
activation_required |
bool
|
Whether the user still needs to confirm via the activation link. |
activation_url |
str | None
|
Front-end URL the user must
visit. |
access_token |
str | None
|
Short-lived JWT. Only set
when |
refresh_token |
str | None
|
Long-lived JWT. Only set
when |
SignupSchema
¶
Bases: BaseSchema
Request body for POST /auth/signup.
Carries the credentials and the optional display name a new
account starts with. The email is normalized to lowercase
before insert (matches the unique-index convention every SDK
user table follows); the password is hashed with bcrypt by
:class:tempest_fastapi_sdk.PasswordUtils and never stored
in plaintext.
Attributes:
| Name | Type | Description |
|---|---|---|
email |
EmailStr
|
Login identifier — validated by
|
password |
str
|
Plaintext password. Length floor is
enforced both here (schema-level) and inside
:class: |
name |
str | None
|
Optional display name shown in the
admin UI / front-end profile. |
UserAuthService
¶
UserAuthService(
*,
user_model: type[BaseUserModel],
token_model: type[BaseUserTokenModel],
auth_settings: AuthSettings,
jwt_settings: JWTSettings,
email: EmailUtils | None = None,
passwords: PasswordUtils | None = None,
jwt: JWTUtils | None = None,
db: AsyncDatabaseManager | None = None,
)
Compose UserModel + UserTokenModel into a full auth flow.
Example:
>>> service = UserAuthService(
... db=db,
... user_model=UserModel,
... token_model=UserTokenModel,
... auth_settings=settings,
... jwt_settings=settings,
... email=email_utils,
... )
>>> async with db.get_session_context() as s:
... result = await service.signup(s, payload)
Every method takes the active AsyncSession explicitly so
callers control the transaction boundary — the service never
opens its own session.
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
Concrete user model
— usually |
required |
token_model
|
type[BaseUserTokenModel]
|
Concrete token
model — usually |
required |
auth_settings
|
AuthSettings
|
The mixin populating activation / reset behavior. |
required |
jwt_settings
|
JWTSettings
|
The mixin populating signing keys and TTLs. |
required |
email
|
EmailUtils | None
|
Configured email helper.
When |
None
|
passwords
|
PasswordUtils | None
|
Override for tests; defaults to a fresh instance. |
None
|
jwt
|
JWTUtils | None
|
Override for tests; defaults
to one built from |
None
|
db
|
AsyncDatabaseManager | None
|
Optional handle for services that open their own sessions inside helpers like background tasks. |
None
|
Source code in tempest_fastapi_sdk/auth/service.py
BaseController
¶
Bases: Generic[ServiceT, ResponseT]
Thin orchestration layer between routers and services.
Following the SDK layering rules (router → controller → service → repository), controllers are kept present even when no orchestration is required so the import graph stays uniform. Override methods here when a single endpoint needs to call multiple services or apply cross-cutting policy; leave the pass-throughs untouched otherwise.
Generic parameters
ServiceT: The concrete service class. ResponseT: The response schema returned to the router.
Attributes:
| Name | Type | Description |
|---|---|---|
service |
ServiceT
|
The service the controller delegates to. |
Initialize the controller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
ServiceT
|
The service to delegate to. |
required |
Source code in tempest_fastapi_sdk/controllers/base.py
BaseIntEnum
¶
Bases: _EnumHelpers, int, Enum
Base class for integer-valued enums.
Mixing in int makes every member a genuine integer instance, so
members compare equal to their values (Member == 1), serialize
cleanly outside Pydantic, and bind directly to Integer database
columns as their value.
BaseStrEnum
¶
Bases: _EnumHelpers, str, Enum
Base class for string-valued enums.
Note
Deliberately a str + Enum mixin rather than
:class:enum.StrEnum. The two differ in str(member)
("Cls.MEMBER" here vs. the bare value under StrEnum);
keeping the mixin form preserves the behaviour consumers already
rely on across services.
Mixing in str makes every member a genuine string instance, so
members compare equal to their values (Member == "VALUE"),
serialize cleanly outside Pydantic, and bind directly to String
database columns as their value.
JSONFormatter
¶
Bases: Formatter
Render every log record as a single-line JSON object.
Standard LogRecord fields are mapped to timestamp,
level, logger and message. The current request ID
(when present) is attached as request_id. Any additional
keyword passed to the logger via extra={...} becomes a
top-level key in the JSON payload.
AlembicHelper
¶
High-level wrapper around the Alembic command surface.
Encapsulates a single alembic.ini configuration and exposes
the operations that matter for day-to-day work — upgrade,
downgrade, revision authoring, schema-vs-models check — without
leaking Alembic internals into application code.
All methods are synchronous because Alembic itself is sync; run
them from CLI scripts or from FastAPI's startup hook via
asyncio.to_thread if you must call them from async code.
Attributes:
| Name | Type | Description |
|---|---|---|
config_path |
str
|
Path to the |
Initialize the helper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
str
|
Path to |
'alembic.ini'
|
db_url
|
str | None
|
If provided, overrides
|
None
|
Source code in tempest_fastapi_sdk/db/migrations.py
AsyncDatabaseManager
¶
AsyncDatabaseManager(
db_url: str,
*,
echo: bool = False,
pool_size: int = 10,
max_overflow: int = 20,
pool_recycle: int = 3600,
connect_args: dict[str, Any] | None = None,
poolclass: type[Pool] | None = None,
**engine_kwargs: Any,
)
Manage the async SQLAlchemy engine and session lifecycle.
Handles engine creation tailored to the database backend (SQLite
gets check_same_thread=False by default, everything else
gets a pooled config), session factory construction, and table
create/drop helpers. Designed to be instantiated once per
application and reused across requests.
Backend detection uses sqlalchemy.engine.make_url so URLs
like sqlite+aiosqlite://... are matched precisely without
relying on substring tricks.
Attributes:
| Name | Type | Description |
|---|---|---|
is_sqlite |
bool
|
Whether the URL targets a SQLite backend. |
The connection URL itself is stored on a private attribute so it
never leaks through repr() or accidental logging. Use the
:attr:db_url_safe property when a redacted form is needed.
Initialize the manager (does not open connections yet).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db_url
|
str
|
The database connection URL. |
required |
echo
|
bool
|
Whether to emit SQL to stdout. |
False
|
pool_size
|
int
|
Number of permanent connections in the pool. Ignored for SQLite URLs. |
10
|
max_overflow
|
int
|
Extra connections allowed above the pool size. Ignored for SQLite URLs. |
20
|
pool_recycle
|
int
|
Recycle connections older than this many seconds. Ignored for SQLite URLs. |
3600
|
connect_args
|
dict[str, Any] | None
|
Driver-level
arguments forwarded to |
None
|
poolclass
|
type[Pool] | None
|
Override SQLAlchemy's
default pool class. Useful for tests
( |
None
|
**engine_kwargs
|
Any
|
Any additional keyword arguments are
passed through to |
{}
|
Source code in tempest_fastapi_sdk/db/connection.py
AuditMixin
¶
Add created_by / updated_by foreign-key columns.
Tracks which user (by UUID) last touched a row. The mixin only declares the columns — populating them is the application's responsibility, typically inside the service layer (where the current user is in scope) right before calling the repository.
Attributes:
| Name | Type | Description |
|---|---|---|
created_by |
UUID | None
|
UUID of the user that created the row. Nullable for system-generated rows. |
updated_by |
UUID | None
|
UUID of the user that last updated the row. Nullable until the first update. |
BaseModel
¶
Bases: AsyncAttrs, DeclarativeBase
Abstract base for every SQLAlchemy model in the application.
Every concrete model inherits the four columns required by the
SDK conventions: id (UUID primary key, cross-DB portable),
is_active (soft-delete flag), created_at and updated_at
(timezone-aware timestamps managed by the database).
The class is marked __abstract__ so SQLAlchemy will not try to
map it directly. Concrete subclasses get an auto-generated
__tablename__ from their class name (e.g. UserModel →
user, OrderItemModel → order_item); the Model
suffix is stripped and the remainder is snake-cased. Explicit
__tablename__ declarations still win when set.
Equality and hashing use (type, id) so the same row loaded
across different sessions compares equal — useful in tests and
sets. Unflushed instances (id is None) fall back to Python
identity.
Attributes:
| Name | Type | Description |
|---|---|---|
metadata |
MetaData
|
Configured with :data: |
id |
UUID
|
Primary key. Generated as UUID v4. Uses
|
is_active |
bool
|
Whether the record is active. Defaults
to |
created_at |
datetime
|
Creation timestamp populated by the database on insert. |
updated_at |
datetime
|
Last-update timestamp refreshed by the database on every update. |
BaseRepository
¶
BaseRepository(
session: AsyncSession,
*,
model: type[ModelType],
not_found_exception: type[AppException] = NotFoundException,
not_found_message: str | None = None,
create_conflict_message: str | None = None,
update_conflict_message: str | None = None,
bulk_create_conflict_message: str | None = None,
bulk_update_conflict_message: str | None = None,
)
Bases: Generic[ModelType]
Base async repository with generic CRUD operations.
Instantiate directly for plain CRUD (BaseRepository(session,
model=UserModel)) or subclass when adding custom queries — the
subclass forwards model / not_found_exception to
super().__init__ instead of declaring class attributes. The
constructor signature is the contract; there are no magic class
attributes to override.
The default filter logic supports equality on every column plus the following conventions:
name(string) → case-insensitiveILIKE %value%search.boolvalues →.is_(value)(correct SQL boolean check).listvalues →.in_(values)membership.datevalues →func.date(column) == valuewhole-day match.start_in/end_in(date) → range filter against the model'sdatecolumn when present, falling back tocreated_at.
All error messages can be customized per repository instance via
the constructor kwargs (not_found_message,
create_conflict_message, etc.); when omitted, sensible defaults
derived from self.model.__name__ are used.
The same three abstract mappers map_to_schema / map_to_model
/ map_to_response are kept so concrete repositories own the
translation between ORM rows and DTOs.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
type[ModelType]
|
The SQLAlchemy model class operated on. |
not_found_exception |
type[AppException]
|
Exception class raised when single-record lookups miss. |
session |
AsyncSession
|
The async database session. |
Initialize the repository.
Every *_message kwarg is optional — when not provided, the
repository falls back to a generic message derived from the
model class name (e.g. "User not found",
"Conflict creating User").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
The async database session. |
required |
model
|
type[ModelType]
|
The SQLAlchemy model class this repository operates on. Required. |
required |
not_found_exception
|
type[AppException]
|
Exception class
raised when single-record lookups miss. Defaults to
:class: |
NotFoundException
|
not_found_message
|
str | None
|
Message used when |
None
|
create_conflict_message
|
str | None
|
Message used when
|
None
|
update_conflict_message
|
str | None
|
Message used when
|
None
|
bulk_create_conflict_message
|
str | None
|
Message used
when |
None
|
bulk_update_conflict_message
|
str | None
|
Message used
when |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
Source code in tempest_fastapi_sdk/db/repository.py
BaseUserModel
¶
Bases: BaseModel
Abstract user table with the columns the admin auth flow needs.
Inherits id/is_active/created_at/updated_at from
:class:BaseModel and adds:
email(unique, indexed) — login identifier. Always stored lowercased; helpers handle the normalization.hashed_password— bcrypt hash produced by :class:tempest_fastapi_sdk.PasswordUtils. Use :meth:set_passwordto write and :meth:check_passwordto verify so the hashing strategy stays consistent across callers.is_admin— gate enforced by the admin auth backend; only users withis_admin=Truemay log in to/admin.last_login_at— populated by the admin login view on every successful authentication.
The class is marked __abstract__ so SQLAlchemy does not try
to map it directly; concrete projects subclass it and either keep
the auto-derived __tablename__ (user) or override it.
Attributes:
| Name | Type | Description |
|---|---|---|
email |
str
|
Login identifier. Unique. 320 chars max (RFC 5321 mailbox limit). |
hashed_password |
str
|
Bcrypt hash; never store plaintext. |
is_admin |
bool
|
Whether the user can access the admin site. |
last_login_at |
datetime | None
|
Last successful login timestamp. |
BaseUserTokenModel
¶
Bases: BaseModel
Abstract one-shot token used by the bundled auth flows.
Concrete subclasses pick the __tablename__ (user_tokens
by convention) and add an FK to the project's concrete
UserModel. The SDK never stores the plaintext token — only
its hash via
:func:tempest_fastapi_sdk.hash_opaque_token. The plaintext
is returned exactly once (at creation) so it can be embedded
in the activation / reset link.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
Foreign key to the user this token authorizes. Inherits the project's user table name — concrete subclasses set the FK target explicitly. |
token_hash |
str
|
SHA-256 hash of the plaintext token. Indexed + unique so lookups by hash are fast and duplicates impossible. |
purpose |
str
|
One of :class: |
expires_at |
datetime
|
UTC timestamp the token becomes
invalid. |
used_at |
datetime | None
|
UTC timestamp the token was redeemed. Non-null means the token is spent and must not be accepted again. |
SoftDeleteMixin
¶
Add a deleted_at timestamp for non-destructive deletes.
Pairs with the canonical is_active flag on
:class:tempest_fastapi_sdk.BaseModel: is_active toggles
visibility quickly while deleted_at records when the soft
delete happened (useful for audit and retention policies).
A row is considered "alive" when deleted_at IS NULL. Filtering
is the caller's responsibility — the mixin keeps the column
declarative-only so it composes with arbitrary query strategies
(global filters, partial indexes, repository hooks).
Attributes:
| Name | Type | Description |
|---|---|---|
deleted_at |
datetime | None
|
Timestamp of the soft delete,
or |
UserTokenPurpose
¶
Bases: StrEnum
What a token authorizes when redeemed.
Each value maps to a distinct flow exposed by
:class:tempest_fastapi_sdk.auth.UserAuthService:
ACTIVATION— confirm the email address the user signed up with.PASSWORD_RESET— let the user pick a new password without the old one.EMAIL_VERIFICATION— re-verify after the email was changed.
AppException
¶
AppException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: HTTPException
Base exception for all application-level errors.
Concrete projects raise either a domain-specific subclass (kept
around for except DomainError matching) or the base directly,
passing code / status_code / message via constructor
keyword arguments. Class-level attributes are the defaults each
constructor argument falls back to, never required overrides::
class UserNotFoundError(NotFoundException):
"""Subclass exists only for isinstance/except matching."""
raise UserNotFoundError(
"Usuário não encontrado",
code="USER_NOT_FOUND",
details={"email": email},
)
The matching exception handler (see
:mod:tempest_fastapi_sdk.api.handlers) emits the JSON shape::
{
"detail": "<message>",
"code": "<code>",
"details": {"<any>": "<context>"}
}
Class attributes (defaults the constructor falls back to): status_code (int): HTTP status code. message (str): Default human-readable message. code (str): Stable, machine-readable identifier.
Instance attributes
status_code (int): The status code attached to this instance. code (str): The error code attached to this instance. details (dict[str, Any]): Free-form context attached to the response payload.
Initialize the exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str | None
|
Override the class-level message. |
None
|
code
|
str | None
|
Override the class-level error code on this instance only — leaves other instances of the same class untouched. |
None
|
status_code
|
int | None
|
Override the class-level HTTP status code on this instance only. |
None
|
details
|
dict[str, Any] | None
|
Structured context to attach to the JSON response. |
None
|
headers
|
dict[str, str] | None
|
Optional HTTP headers to include in the response. |
None
|
Source code in tempest_fastapi_sdk/exceptions/base.py
ConflictException
¶
ConflictException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when a write would violate a uniqueness/integrity rule.
Typically surfaced by the repository when SQLAlchemy raises an
IntegrityError on insert/update.
Source code in tempest_fastapi_sdk/exceptions/base.py
ExpiredTokenException
¶
ExpiredTokenException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: UnauthorizedException
Raised when a JWT's exp claim is in the past.
Source code in tempest_fastapi_sdk/exceptions/base.py
FileTooLargeException
¶
FileTooLargeException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when an uploaded file exceeds the configured size limit.
Source code in tempest_fastapi_sdk/exceptions/base.py
ForbiddenException
¶
ForbiddenException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when the caller is authenticated but lacks permission.
Source code in tempest_fastapi_sdk/exceptions/base.py
InvalidFileTypeException
¶
InvalidFileTypeException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when an uploaded file's extension or MIME is not allowed.
Source code in tempest_fastapi_sdk/exceptions/base.py
InvalidTokenException
¶
InvalidTokenException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: UnauthorizedException
Raised when a JWT fails signature or claim validation.
Source code in tempest_fastapi_sdk/exceptions/base.py
NotFoundException
¶
NotFoundException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when a single resource cannot be located.
Use for get_by_id / get_by_email style lookups. NEVER use
for collection endpoints — those should return [] instead.
Source code in tempest_fastapi_sdk/exceptions/base.py
TooManyRequestsException
¶
TooManyRequestsException(
message: str | None = None,
*,
retry_after_seconds: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when a client exceeds a rate limit or attempt budget.
Carries an optional Retry-After header (seconds) and mirrors
the same value under details["retry_after_seconds"] so clients
can back off without parsing headers. Used by
:class:tempest_fastapi_sdk.utils.AttemptThrottle and suitable for
any throttled flow (login, OTP, code verification).
Initialize the exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str | None
|
Override the class-level message. |
None
|
retry_after_seconds
|
int | None
|
Cooldown in seconds. When
given, sets the |
None
|
details
|
dict[str, Any] | None
|
Structured context. |
None
|
headers
|
dict[str, str] | None
|
Extra response headers;
merged with the |
None
|
Source code in tempest_fastapi_sdk/exceptions/too_many_requests.py
UnauthorizedException
¶
UnauthorizedException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when the caller is not authenticated.
Use for missing/invalid/expired credentials. For "authenticated
but not allowed" cases, use
:class:tempest_fastapi_sdk.exceptions.forbidden.ForbiddenException.
Source code in tempest_fastapi_sdk/exceptions/base.py
ValidationException
¶
ValidationException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when input fails a business rule beyond Pydantic.
Pydantic emits 422 automatically for schema validation; use this for downstream rules that only the service layer can enforce.
Source code in tempest_fastapi_sdk/exceptions/base.py
BasePaginationFilterSchema
¶
Bases: BaseSchema
Base filter schema for paginated list endpoints.
Subclass it to add domain-specific filter fields. The base
get_conditions method returns every populated field except
the pagination/sort keys, which is the contract expected by
:class:tempest_fastapi_sdk.db.repository.BaseRepository.paginate.
Field names and defaults mirror the BaseRepository.paginate
keyword arguments so passing the schema straight through works
without renaming:
.. code-block:: python
result = await repo.paginate(
filters=f.get_conditions(),
order_by=f.order_by,
page=f.page,
page_size=f.page_size,
ascending=f.ascending,
)
Attributes:
| Name | Type | Description |
|---|---|---|
page |
int
|
The page number to retrieve (1-indexed). |
page_size |
int
|
The number of items per page. |
order_by |
str | None
|
The column name to order by. |
ascending |
bool
|
Whether to order ascending. Ignored when
|
is_active |
bool | None
|
Filter by active status. |
BasePaginationSchema
¶
Bases: BaseSchema, Generic[T]
Generic envelope returned by paginated endpoints.
Wraps the page of items together with the pagination metadata
the frontend needs to render controls. Field names match the
request-side :class:BasePaginationFilterSchema and the
repository keyword arguments, so the round-trip stays free of
renames.
Attributes:
| Name | Type | Description |
|---|---|---|
items |
list[T]
|
The items in the current page. |
total |
int
|
The total number of items across all pages. |
page |
int
|
The current page number (1-indexed). |
page_size |
int
|
The number of items per page. |
pages |
int
|
The total number of pages. |
BaseResponseSchema
¶
Bases: BaseSchema
Response schema with the four columns every ORM record carries.
Used as the parent of any *ResponseSchema whose payload mirrors
a row from a table inheriting from
:class:tempest_fastapi_sdk.db.model.BaseModel. created_at and
updated_at are normalized to UTC after validation so the API
always emits timezone-aware timestamps regardless of how the DB
driver returned them.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
UUID
|
The unique identifier of the record. |
is_active |
bool
|
Whether the record is active (soft-delete convention). |
created_at |
datetime
|
The creation timestamp, normalized to UTC. |
updated_at |
datetime
|
The last update timestamp, normalized to UTC. |
BaseSchema
¶
Bases: BaseModel
Base class for every Pydantic schema in an application.
Centralizes the configuration that all DTOs share: ignore extra fields, allow building schemas from ORM attributes, serialize enum values, strip whitespace from strings, and validate assignments after construction.
Attributes:
| Name | Type | Description |
|---|---|---|
model_config |
ConfigDict
|
The Pydantic configuration. |
CursorPaginationFilterSchema
¶
Bases: BaseSchema
Request filter for cursor-based pagination endpoints.
Cursor pagination scales better than offset pagination on large
tables (no COUNT(*), stable under concurrent inserts) at the
cost of losing random-access semantics. Subclass to add domain
filters; :meth:get_conditions strips the cursor/sort keys
automatically.
Attributes:
| Name | Type | Description |
|---|---|---|
cursor |
str | None
|
Opaque cursor returned by the previous
page. |
limit |
int
|
Maximum number of items to return. |
order_by |
str
|
Column to sort by. Must be a sortable column
with a stable secondary tie-break ( |
ascending |
bool
|
Whether to sort ascending. Defaults to
|
CursorPaginationSchema
¶
Bases: BaseSchema, Generic[T]
Generic envelope returned by cursor-paginated endpoints.
Attributes:
| Name | Type | Description |
|---|---|---|
items |
list[T]
|
The items in the current page. |
next_cursor |
str | None
|
Cursor to request the next page,
or |
has_more |
bool
|
Whether another page is available. |
limit |
int
|
The page size used to produce this payload. |
LogEntrySchema
¶
Bases: BaseSchema
A single structured log record parsed from a JSON log file.
The SDK's :class:tempest_fastapi_sdk.JSONFormatter writes one JSON
object per line. This schema mirrors its core fields and accepts any
additional extra={...} keys (e.g. path, request_id,
http_500) via extra="allow" so nothing is silently dropped
by the /logs endpoint.
Attributes:
| Name | Type | Description |
|---|---|---|
timestamp |
str
|
ISO-8601 UTC timestamp ( |
level |
str
|
Log level name ( |
logger |
str
|
Name of the logger that emitted the record. |
message |
str
|
The formatted log message. |
request_id |
str | None
|
Correlation ID when present. |
exception |
str | None
|
Formatted traceback when the record
carried |
BaseService
¶
Bases: Generic[RepositoryT, ResponseT]
Thin business-logic layer wrapping a :class:BaseRepository.
The default implementation exposes CRUD pass-through methods that
delegate to the repository and apply map_to_response so the
surface matches what routers/controllers consume. Concrete
services should override methods that involve orchestration
(multi-repository writes, external side effects, domain rules);
pure pass-through methods can be left untouched.
Generic parameters
RepositoryT: The concrete repository class. ResponseT: The response schema returned by the service.
Attributes:
| Name | Type | Description |
|---|---|---|
repository |
RepositoryT
|
The repository the service delegates to. |
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
repository
|
RepositoryT
|
The repository to delegate to. |
required |
Source code in tempest_fastapi_sdk/services/base.py
MemorySessionStore
¶
In-process :class:SessionStore for dev, tests, single-replica.
Stores sessions in a dict keyed by their hashed id; a secondary
index by user_id powers list_by_user /
delete_by_user without scanning. Expired rows are pruned on
access — no background task needed.
Initialize the in-memory store.
Source code in tempest_fastapi_sdk/sessions/store.py
RedisSessionStore
¶
:class:SessionStore backed by an async redis client.
Schema:
- Each session is stored at
{prefix}sess:{hash}as JSON with a TTL set to(expires_at - now)so Redis evicts the key on its own — no janitor process needed. - The user → session index lives at
{prefix}user:{user_id}as a Redis SET of session hashes. Entries are removed ondeleteand the whole SET is dropped ondelete_by_user.
Requires the [cache] extra so the redis async client is
available.
Initialize the Redis-backed store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
Redis
|
Async Redis client (e.g.
|
required |
prefix
|
str
|
Key prefix so session keys do not collide with other cached data. |
'tempest:'
|
Source code in tempest_fastapi_sdk/sessions/store.py
Session
¶
Bases: BaseSchema
A live server-side session.
Stored in the configured :class:SessionStore keyed by the
SHA-256 hash of the session id (the plaintext lives only in the
cookie). Mirrors what every session-backed auth flow needs:
user identity, lifetime bounds, originating client metadata for
revocation UX ("you're signed in on Chrome from São Paulo"),
and a free-form data bag for app-level state.
Attributes:
| Name | Type | Description |
|---|---|---|
session_id |
str
|
SHA-256 hex digest of the cookie value —
NOT the plaintext. The plaintext leaves over
|
user_id |
UUID
|
Owner of the session. |
created_at |
datetime
|
UTC timestamp when the session was issued. |
expires_at |
datetime
|
UTC timestamp after which the
session is rejected. Refreshed by
:meth: |
last_seen_at |
datetime
|
UTC timestamp of the last request that resolved the session. Updated by the middleware on every hit. |
ip |
str | None
|
Client IP recorded at session creation. Useful for the "list active sessions" UI. |
user_agent |
str | None
|
User-Agent header recorded at session creation. |
data |
dict[str, Any]
|
Arbitrary JSON-serializable bag — shopping cart id, last-seen route, locale preference, etc. |
SessionAuth
¶
SessionAuth(
*,
user_model: type[BaseUserModel],
store: SessionStore,
settings: SessionSettings,
passwords: PasswordUtils | None = None,
)
Server-side session lifecycle orchestrator.
Mount one instance per FastAPI app. Stateless — the only
state lives in the injected :class:SessionStore and the
project's UserModel table.
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
Concrete user model
(typically |
required |
store
|
SessionStore
|
Persistence backend
(:class: |
required |
settings
|
SessionSettings
|
TTL / cookie / rotation flags driving the lifecycle. |
required |
passwords
|
PasswordUtils | None
|
Override for tests;
defaults to a fresh |
None
|
Source code in tempest_fastapi_sdk/sessions/service.py
SessionLoginSchema
¶
SessionMiddleware
¶
SessionMiddleware(
app: ASGIApp, *, session_auth: SessionAuth, settings: SessionSettings
)
Bases: BaseHTTPMiddleware
ASGI middleware that resolves the session cookie per request.
Attach with::
app.add_middleware(
SessionMiddleware,
session_auth=session_auth,
settings=session_settings,
)
After the middleware runs, every handler in the chain can read
request.state.session — a :class:Session instance when
the cookie was valid, None otherwise. Handlers that require
authentication should depend on
:func:make_session_dependency instead of poking
request.state directly so missing sessions raise a clean
401 envelope.
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
Wrapped ASGI app — Starlette passes this
automatically when used with |
required |
session_auth
|
SessionAuth
|
Configured service used to resolve cookies into sessions. |
required |
settings
|
SessionSettings
|
Read for the cookie name. |
required |
Source code in tempest_fastapi_sdk/sessions/middleware.py
SessionResponseSchema
¶
Bases: BaseSchema
Body returned by POST /auth/session/login.
The session id itself is delivered via Set-Cookie —
deliberately NOT in this body — so JavaScript cannot read it
(HttpOnly cookies). The body carries everything the frontend
actually needs to render an authenticated state.
SessionStore
¶
Bases: Protocol
Persistence protocol every session backend implements.
SessionSummarySchema
¶
Bases: BaseSchema
Public-safe projection of a :class:Session used by list endpoints.
Drops session_id (so revealing the list does NOT leak any
secret) and renames the visible identifier to id — a stable
UUID derived from the hashed session id by truncation, suitable
for DELETE /auth/session/{id} revocation calls.
AuthSettings
¶
Bases: BaseSettings
Configuration for the bundled signup / activation / reset flows.
Consumed by :class:tempest_fastapi_sdk.auth.UserAuthService
and :func:tempest_fastapi_sdk.make_auth_router. Each flag
has a sensible production default; flip AUTH_AUTO_ACTIVATE
or AUTH_RETURN_TOKEN_IN_RESPONSE only in dev / CI.
BaseAppSettings
¶
Bases: BaseSettings
Shared configuration for Settings classes across projects.
Provides the canonical pydantic-settings config block; concrete projects subclass this and add their domain-specific fields (database URLs, secrets, third-party keys, etc.).
The defaults:
env_file=".env"— load environment variables from a local.envfile when present.extra="ignore"— silently drop unexpected env vars instead of raising at startup.case_sensitive=True— env var names are matched exactly.frozen=True— settings are immutable after construction.str_strip_whitespace=True— trim accidental whitespace around env values.from_attributes=True— allow building from objects with attribute access (rarely needed for settings, but harmless).
Attributes:
| Name | Type | Description |
|---|---|---|
model_config |
SettingsConfigDict
|
The pydantic-settings configuration. |
CORSSettings
¶
Bases: BaseSettings
CORS middleware configuration.
.. warning::
The default CORS_ORIGINS=["*"] is permissive on purpose
so local development works out of the box. Never ship
this default to production — set CORS_ORIGINS to the
explicit list of trusted frontend origins. "*" is also
incompatible with CORS_ALLOW_CREDENTIALS=True (browsers
ignore credentialed requests sent to a wildcard origin).
DatabaseSettings
¶
Bases: BaseSettings
SQLAlchemy database connection configuration.
EmailSettings
¶
Bases: BaseSettings
SMTP / transactional email configuration.
Mirrors the constructor arguments of
:class:tempest_fastapi_sdk.EmailUtils so a service can wire it up
with EmailUtils(**settings.email_kwargs()).
JWTSettings
¶
Bases: BaseSettings
JWT signing and verification configuration.
LogSettings
¶
Bases: BaseSettings
Structured logging configuration.
MinIOSettings
¶
Bases: BaseSettings
MinIO / S3-compatible object storage configuration.
Consumed by :class:tempest_fastapi_sdk.AsyncMinIOClient. The
same shape works for any S3-compatible target (AWS S3, MinIO,
Backblaze B2, Cloudflare R2, Wasabi, DigitalOcean Spaces).
RabbitMQSettings
¶
Bases: BaseSettings
RabbitMQ / FastStream broker configuration.
RedisSettings
¶
Bases: BaseSettings
Redis connection configuration.
ServerSettings
¶
Bases: BaseSettings
HTTP server bind configuration.
SessionSettings
¶
Bases: BaseSettings
Server-side session cookie + storage configuration.
Consumed by :class:tempest_fastapi_sdk.SessionAuth,
:class:tempest_fastapi_sdk.SessionMiddleware, and
:func:tempest_fastapi_sdk.make_session_router. Defaults assume
HTTPS in production (SESSION_COOKIE_SECURE=True) and a
same-site SaaS topology (SESSION_COOKIE_SAMESITE="lax") —
relax both only for local HTTP development.
TaskIQSettings
¶
Bases: BaseSettings
TaskIQ broker / result backend configuration.
Use this when the TaskIQ broker is not the same RabbitMQ /
Redis instance covered by :class:RabbitMQSettings /
:class:RedisSettings.
TokenSettings
¶
Bases: BaseSettings
Shared-secret X-Token configuration.
Used by :func:tempest_fastapi_sdk.make_token_dependency for
internal service-to-service authentication. Validation is performed
with :func:hmac.compare_digest.
UploadSettings
¶
Bases: BaseSettings
File upload constraints.
Mirrors the constructor arguments of
:class:tempest_fastapi_sdk.UploadUtils.
WebPushSettings
¶
Bases: BaseSettings
Web Push / VAPID configuration.
Mirrors the constructor arguments of
:class:tempest_fastapi_sdk.WebPushDispatcher.
WebSocketSettings
¶
Bases: BaseSettings
WebSocket router configuration.
Consumed by :func:tempest_fastapi_sdk.make_websocket_router and
:class:tempest_fastapi_sdk.WebSocketHub. Defaults are tuned for
typical browser ↔ FastAPI deployments — heartbeats every 30s,
drop after 60s without pong, five concurrent connections per
user.
EventStream
¶
Async in-memory queue feeding one SSE HTTP connection.
A handler builds one stream per client request, publish-es
events from anywhere in the application (background tasks,
websockets, dependency callbacks), and passes :meth:stream to
:func:sse_response. A None enqueued by :meth:close
terminates the iteration so the response completes cleanly.
Heartbeats are emitted as SSE comments
(: keepalive lines) when the queue stays empty for longer
than heartbeat_seconds; this keeps load-balancers from
closing idle TCP connections.
Attributes:
| Name | Type | Description |
|---|---|---|
heartbeat_seconds |
float | None
|
Idle interval that triggers
a comment heartbeat. |
Initialize the stream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
heartbeat_seconds
|
float | None
|
Idle interval before a comment heartbeat is emitted. |
15.0
|
Source code in tempest_fastapi_sdk/sse/event_stream.py
ServerSentEvent
dataclass
¶
ServerSentEvent(
data: Any = "",
event: str | None = None,
id: str | None = None,
retry: int | None = None,
comment: str | None = None,
)
A single SSE frame.
Encodes to the line-based wire format defined by the spec
(https://html.spec.whatwg.org/multipage/server-sent-events.html).
data may be a string, bytes, or any JSON-serializable Python
object — non-string payloads are JSON-encoded before transmission.
Attributes:
| Name | Type | Description |
|---|---|---|
data |
Any
|
The event payload. |
event |
str | None
|
Optional event name; the browser routes
|
id |
str | None
|
Optional |
retry |
int | None
|
Reconnection delay (milliseconds) the browser should use after a connection drop. |
comment |
str | None
|
Optional comment line prepended to the
frame (renders as |
AsyncMinIOClient
¶
AsyncMinIOClient(
endpoint: str,
access_key: str,
secret_key: str,
*,
default_bucket: str = "uploads",
secure: bool = False,
region: str = "us-east-1",
session_token: str | None = None,
)
Async-friendly facade over minio.Minio.
Use as an async context manager when you want explicit cleanup,
or hold a long-lived instance on the FastAPI app — the
underlying Minio client is thread-safe and reuses its
connection pool.
Example:
>>> from tempest_fastapi_sdk import AsyncMinIOClient
>>> storage = AsyncMinIOClient(
... endpoint="localhost:9000",
... access_key="minioadmin",
... secret_key="minioadmin",
... default_bucket="uploads",
... )
>>> await storage.ensure_bucket()
>>> await storage.put_object("hello.txt", b"world")
>>> body = await storage.get_object_bytes("hello.txt")
>>> assert body == b"world"
Initialize the client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
endpoint
|
str
|
|
required |
access_key
|
str
|
S3 access key. |
required |
secret_key
|
str
|
S3 secret key. |
required |
default_bucket
|
str
|
Bucket used by object operations
when no explicit |
'uploads'
|
secure
|
bool
|
Use HTTPS when |
False
|
region
|
str
|
S3 region. Match the bucket region for AWS S3; any value works for MinIO. |
'us-east-1'
|
session_token
|
str | None
|
Optional STS session token for temporary credentials. |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/storage/minio_client.py
ObjectStat
dataclass
¶
ObjectStat(
bucket: str,
key: str,
size: int,
etag: str | None,
content_type: str | None,
last_modified: datetime | None,
metadata: dict[str, str],
raw: Object,
)
Subset of object metadata returned by :meth:AsyncMinIOClient.stat_object.
The full minio.datatypes.Object instance is also reachable via
the raw attribute when you need the long tail of fields
(version id, owner, restoration state, etc.).
Attributes:
| Name | Type | Description |
|---|---|---|
bucket |
str
|
Bucket the object lives in. |
key |
str
|
Object key (S3 path). |
size |
int
|
Size in bytes. |
etag |
str | None
|
Server-side ETag (quotes stripped). |
content_type |
str | None
|
MIME type recorded at upload. |
last_modified |
datetime | None
|
Last modification timestamp in UTC. |
metadata |
dict[str, str]
|
User metadata keyed without the
|
raw |
Object
|
Underlying |
AttemptThrottle
¶
AttemptThrottle(
backend: ThrottleBackend,
*,
max_attempts: int,
window_seconds: int,
namespace: str = "throttle",
fail_open: bool = True,
)
Fixed-window failure counter over an injected async KV backend.
Initialize the throttle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backend
|
ThrottleBackend
|
Async KV store (e.g.
|
required |
max_attempts
|
int
|
Failures allowed before a key is
blocked. Must be |
required |
window_seconds
|
int
|
Sliding window length (also the TTL
applied on the first failure). Must be |
required |
namespace
|
str
|
Key prefix so multiple throttles can share a backend without colliding. |
'throttle'
|
fail_open
|
bool
|
When |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in tempest_fastapi_sdk/utils/throttle.py
CircuitOpenError
¶
Bases: Exception
Raised when the circuit-breaker rejects a call.
Carries the host that tripped the breaker so callers can branch on it (e.g. fall back to a cache or a queue).
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
The host whose breaker is open. |
required |
Source code in tempest_fastapi_sdk/utils/http_client.py
CPUMetrics
dataclass
¶
CPUMetrics(
percent: float,
cores_logical: int,
cores_physical: int,
load_average: tuple[float, float, float] | None = None,
)
CPU usage snapshot.
Attributes:
| Name | Type | Description |
|---|---|---|
percent |
float
|
Aggregate CPU utilization (0-100). |
cores_logical |
int
|
Logical core count (including SMT). |
cores_physical |
int
|
Physical core count. |
load_average |
tuple[float, float, float] | None
|
⅕/15-minute
load averages on POSIX; |
DiskMetrics
dataclass
¶
Disk usage snapshot for a single mount point.
Attributes:
| Name | Type | Description |
|---|---|---|
path |
str
|
The mount point inspected. |
total_bytes |
int
|
Filesystem total capacity. |
used_bytes |
int
|
Used bytes. |
free_bytes |
int
|
Free bytes. |
percent |
float
|
Used percentage (0-100). |
DownloadUtils
¶
Serve files from a base directory as inline or attachment responses.
All disk reads are confined to base_dir: a relative path that
resolves outside it (../ traversal, absolute paths, symlink
escapes) raises :class:NotFoundException rather than leaking
arbitrary files. The same 404 is raised when the target does not
exist or is not a regular file, so callers never have to special-case
the difference between "missing" and "forbidden".
Attributes:
| Name | Type | Description |
|---|---|---|
base_dir |
Path
|
Resolved root every served file must live under. |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_dir
|
Path | str
|
Root directory files are served from. Resolved to an absolute path on construction; the directory is not required to exist yet. |
required |
Source code in tempest_fastapi_sdk/utils/download.py
EmailUtils
¶
EmailUtils(
host: str,
port: int,
*,
from_addr: str,
username: str | None = None,
password: str | None = None,
use_tls: bool = False,
use_starttls: bool = True,
timeout: float = 30.0,
template_dir: str | Path | None = None,
)
Send transactional emails via SMTP.
Connection configuration is supplied at construction time; each
:meth:send call opens a fresh SMTP connection (aiosmtplib's
high-level send helper handles connect/login/quit). For
high-volume scenarios consider holding a persistent connection
via aiosmtplib.SMTP directly.
Attributes:
| Name | Type | Description |
|---|---|---|
host |
str
|
SMTP server hostname. |
port |
int
|
SMTP port. |
from_addr |
str
|
Default sender address used as the |
use_tls |
bool
|
Whether to connect using SSL/TLS from the start (port 465 style). |
use_starttls |
bool
|
Whether to upgrade to TLS via STARTTLS after connect (port 587 style). |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
SMTP server hostname. |
required |
port
|
int
|
SMTP port. Common values: |
required |
from_addr
|
str
|
Default sender address. |
required |
username
|
str | None
|
Auth username. |
None
|
password
|
str | None
|
Auth password. |
None
|
use_tls
|
bool
|
Connect using SSL/TLS immediately. Set
this for port |
False
|
use_starttls
|
bool
|
Upgrade to TLS via STARTTLS after
connect. Set this for port |
True
|
timeout
|
float
|
SMTP socket timeout in seconds. |
30.0
|
template_dir
|
str | Path | None
|
Directory holding Jinja2
templates for :meth: |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/email.py
GPUMetrics
dataclass
¶
GPUMetrics(
index: int,
name: str,
memory_total_bytes: int,
memory_used_bytes: int,
memory_free_bytes: int,
utilization_percent: float,
temperature_celsius: float | None = None,
)
Single-GPU usage snapshot.
Populated by :func:pynvml (NVIDIA only). Non-NVIDIA hosts get an
empty list from :meth:MetricsUtils.gpus.
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
Device index (0-based). |
name |
str
|
Device model name. |
memory_total_bytes |
int
|
VRAM capacity. |
memory_used_bytes |
int
|
VRAM in use. |
memory_free_bytes |
int
|
VRAM free. |
utilization_percent |
float
|
GPU utilization (0-100). |
temperature_celsius |
float | None
|
Core temperature, when reported by the driver. |
HTTPClient
¶
HTTPClient(
*,
base_url: str = "",
timeout: float = 10.0,
retry_policy: RetryPolicy | None = None,
failure_threshold: int = 5,
recovery_seconds: float = 30.0,
default_headers: Mapping[str, str] | None = None,
verify_tls: bool = True,
propagate_request_id: bool = True,
)
Async HTTP client with retries, circuit-breaker and request-id propagation.
Example:
>>> client = HTTPClient(base_url="https://api.example.com")
>>> async with client:
... response = await client.get("/users/me")
... payload: dict[str, Any] = response.json()
The client is safe to share across requests on the same
event loop — internally each call uses the shared
:class:httpx.AsyncClient connection pool.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
Prepended to relative paths. Use empty string to require absolute URLs at the call site. |
''
|
timeout
|
float
|
Per-request timeout in seconds. Overridable per call. |
10.0
|
retry_policy
|
RetryPolicy | None
|
Retry configuration.
|
None
|
failure_threshold
|
int
|
Consecutive 5xx/network errors
that trip the circuit per host. |
5
|
recovery_seconds
|
float
|
Seconds the breaker stays open before allowing one half-open probe. |
30.0
|
default_headers
|
Mapping[str, str] | None
|
Headers
attached to every request (e.g. |
None
|
verify_tls
|
bool
|
Whether to verify TLS certificates.
Default |
True
|
propagate_request_id
|
bool
|
When |
True
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/http_client.py
JWTUtils
¶
JWTUtils(
secret: str,
*,
algorithm: str = "HS256",
default_ttl: timedelta = timedelta(hours=1),
issuer: str | None = None,
)
Encode and decode JWTs using a shared secret.
Every token gets an iat (issued-at) and exp (expiry)
claim populated automatically; the caller is responsible for the
rest (sub, custom claims, etc.). When the helper is created
with issuer=, the iss claim is also added on encode and
verified on decode.
Attributes:
| Name | Type | Description |
|---|---|---|
algorithm |
str
|
The JWT signing algorithm. |
default_ttl |
timedelta
|
Default expiration applied on
:meth: |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
secret
|
str
|
The signing key (HMAC) or private key (RSA/EC). |
required |
algorithm
|
str
|
JWT algorithm. Defaults to |
'HS256'
|
default_ttl
|
timedelta
|
TTL applied by :meth: |
timedelta(hours=1)
|
issuer
|
str | None
|
Value for the |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/jwt.py
LocalUploadStorage
¶
Disk-backed :class:UploadStorage using aiofiles.
Writes chunks under base_dir and refuses keys that resolve
outside the base — same path-traversal protection
:class:UploadUtils already applied. The base_dir is
created (with parents) on instantiation.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_dir
|
Path | str
|
Root directory for all writes. |
required |
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/storage_backends.py
LogUtils
¶
LogUtils(
name: str,
*,
level: str | int = "INFO",
json_output: bool = True,
log_dir: str | Path | None = "logs",
stdout: bool = True,
file_output: bool = True,
)
High-level logging facade used across SDK consumers.
Wraps :func:tempest_fastapi_sdk.configure_logging so callers can
obtain a fully configured JSON logger with one line, and exposes
structured info/warning/error/debug/exception
methods that forward **fields as top-level keys on the JSON
payload via Python's logging.LogRecord.extra.
The class can be used in two flavors:
- Instance API — keeps a configured logger as state and exposes level methods directly. Recommended for service-wide singletons.
- Static helpers — :meth:
configureand :meth:get_loggerfor ad-hoc configuration without tying state to an object.
Attributes:
| Name | Type | Description |
|---|---|---|
logger |
Logger
|
The configured stdlib logger. |
name |
str
|
The logger name. |
Configure and bind a logger to this instance.
Mirrors :func:configure_logging defaults — stdout and file
output are enabled out of the box, writing under logs/.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Logger name. Typically |
required |
level
|
str | int
|
Minimum log level to emit. Accepts
stdlib names ( |
'INFO'
|
json_output
|
bool
|
When |
True
|
log_dir
|
str | Path | None
|
Directory for per-level files.
Defaults to |
'logs'
|
stdout
|
bool
|
Attach the stdout handler. Defaults to
|
True
|
file_output
|
bool
|
Attach the per-level + |
True
|
Source code in tempest_fastapi_sdk/utils/log.py
MemoryMetrics
dataclass
¶
RAM usage snapshot.
Attributes:
| Name | Type | Description |
|---|---|---|
total_bytes |
int
|
Total physical memory. |
used_bytes |
int
|
Memory actively in use. |
available_bytes |
int
|
Memory available for new allocations. |
percent |
float
|
Used percentage (0-100). |
MetricsUtils
¶
Aggregated CPU/RAM/disk/GPU readings for the current host.
Built on top of :mod:psutil (always required by the [metrics]
extra) and pynvml (optional — NVIDIA GPU support degrades to
an empty list when the library is missing or no NVIDIA device is
present).
Every method has a synchronous and an asynchronous variant. Sync
methods call :mod:psutil directly (most calls are non-blocking
or block briefly for sampling); async variants run the same code
via :func:asyncio.to_thread so they never stall the event loop
when a longer sampling interval is requested.
Stateless — instantiation is unnecessary; every method is a classmethod.
MinIOUploadStorage
¶
MinIOUploadStorage(client: AsyncMinIOClient, *, bucket: str | None = None)
:class:UploadStorage backed by :class:AsyncMinIOClient.
Reuses an existing client instance — typically the one wired
on the FastAPI app — so the connection pool is shared. The
bucket falls back to the client's default_bucket.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
AsyncMinIOClient
|
A configured MinIO client. |
required |
bucket
|
str | None
|
Target bucket. |
None
|
Source code in tempest_fastapi_sdk/utils/storage_backends.py
PasswordUtils
¶
Hash and verify passwords using bcrypt.
Stateless utility — instantiate once and reuse across the
application. The cost factor (rounds) controls how slow
hashing is; 12 is a sensible 2026 default. Raise it when CPU
budget allows to keep up with hardware.
Attributes:
| Name | Type | Description |
|---|---|---|
rounds |
int
|
The bcrypt cost factor. |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rounds
|
int
|
The bcrypt cost factor. Higher values make
hashing slower and brute-force attacks harder.
Defaults to |
12
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/password.py
RetryPolicy
dataclass
¶
RetryPolicy(
max_attempts: int = 3,
backoff_initial_seconds: float = 0.5,
backoff_max_seconds: float = 8.0,
retry_statuses: frozenset[int] = (lambda: frozenset({429, 500, 502, 503, 504}))(),
)
Bounded exponential backoff for retried requests.
The first retry sleeps for backoff_initial_seconds; each
subsequent retry doubles the wait, capped at
backoff_max_seconds. Total retries are bounded by
max_attempts (the first try counts).
Attributes:
| Name | Type | Description |
|---|---|---|
max_attempts |
int
|
Total tries including the first.
|
backoff_initial_seconds |
float
|
Sleep before the second attempt. |
backoff_max_seconds |
float
|
Hard cap per sleep. |
retry_statuses |
frozenset[int]
|
HTTP status codes worth
retrying. Defaults to common 5xx; |
SystemMetrics
dataclass
¶
SystemMetrics(
cpu: CPUMetrics,
memory: MemoryMetrics,
disks: list[DiskMetrics] = list(),
gpus: list[GPUMetrics] = list(),
)
Full machine snapshot returned by :meth:MetricsUtils.snapshot.
Attributes:
| Name | Type | Description |
|---|---|---|
cpu |
CPUMetrics
|
CPU usage block. |
memory |
MemoryMetrics
|
RAM usage block. |
disks |
list[DiskMetrics]
|
One entry per inspected path. |
gpus |
list[GPUMetrics]
|
One entry per detected NVIDIA GPU. |
ThrottleBackend
¶
Bases: Protocol
Minimal async key-value contract a throttle backend must satisfy.
Matches the relevant subset of redis.asyncio.Redis.
ThrottleStatus
dataclass
¶
Outcome of a throttle query.
Attributes:
| Name | Type | Description |
|---|---|---|
attempts |
int
|
Failures recorded in the current window. |
blocked |
bool
|
Whether the attempt budget is exhausted. |
retry_after_seconds |
int
|
Seconds until the window resets.
|
UploadResult
dataclass
¶
Outcome of an :meth:UploadStorage.write_stream call.
The key is the canonical identifier for the persisted
object — a path string for the local backend, an S3 key for
MinIO. path is set only when the backend wrote to a real
filesystem location; url only when the backend can mint
a download URL (presigned or static).
Attributes:
| Name | Type | Description |
|---|---|---|
key |
str
|
Identifier used to read the object back. |
size |
int
|
Bytes written. |
path |
Path | None
|
On-disk path when applicable. |
url |
str | None
|
Public or presigned download URL when applicable. |
UploadStorage
¶
Bases: Protocol
Protocol every upload backend implements.
Implementations must be safe to call concurrently — FastAPI routes share the same instance.
UploadUtils
¶
UploadUtils(
upload_dir: Path | str,
*,
max_size_bytes: int | None = None,
allowed_extensions: set[str] | None = None,
allowed_mimetypes: set[str] | None = None,
verify_magic_bytes: bool = False,
chunk_size: int = 1024 * 1024,
)
Persist uploaded files to local disk with opt-in validation.
Validation is incremental: extension and MIME type are checked
against the configured whitelists before reading any bytes; the
file's real content is optionally sniffed from its first bytes
(verify_magic_bytes); and size is enforced as the stream is
consumed so oversized uploads don't fill the disk before being
rejected.
Saved files are streamed in chunks so memory usage stays bounded regardless of the upload size.
Attributes:
| Name | Type | Description |
|---|---|---|
upload_dir |
Path
|
Base directory where files are persisted. Created on instantiation when missing. |
max_size_bytes |
int | None
|
Reject uploads larger than this.
|
allowed_extensions |
set[str] | None
|
Whitelist of file
extensions (lowercase, no dot). |
allowed_mimetypes |
set[str] | None
|
Whitelist of MIME types
(lowercase). |
verify_magic_bytes |
bool
|
When |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
upload_dir
|
Path | str
|
Base directory. Created if missing (parents included). |
required |
max_size_bytes
|
int | None
|
Reject uploads larger than
this. |
None
|
allowed_extensions
|
set[str] | None
|
Whitelist of file
extensions. Leading dots and case are normalized
internally so |
None
|
allowed_mimetypes
|
set[str] | None
|
Whitelist of MIME
types (case-insensitive, e.g. |
None
|
verify_magic_bytes
|
bool
|
Sniff the first bytes of each
upload and reject content that does not match its
declared type / the allow-list. See the class
attribute docs for the caveat. Default |
False
|
chunk_size
|
int
|
Stream read chunk in bytes. Defaults to 1 MiB; raise to trade memory for fewer syscalls. |
1024 * 1024
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/upload.py
WebPushDispatcher
¶
WebPushDispatcher(
vapid_private_key: str,
*,
vapid_subject: str,
ttl_seconds: int = 60,
extra_vapid_claims: dict[str, str] | None = None,
)
Send VAPID-signed Web Push notifications to browser subscribers.
Wraps the synchronous pywebpush library in
:func:asyncio.to_thread so dispatch fits the SDK's async-first
convention. Subscriptions that respond with 404/410 raise
:class:WebPushGoneError so the caller can prune their store;
every other failure raises :class:WebPushError.
Attributes:
| Name | Type | Description |
|---|---|---|
vapid_private_key |
str
|
VAPID private key (PEM or base64url encoded). MUST match the public key advertised to clients. |
vapid_claims |
dict[str, str]
|
Mandatory JWT claims attached
to every push. |
ttl_seconds |
int
|
Default time-to-live applied to each push (the push service buffers the payload for at most this long when the device is offline). |
Initialize the dispatcher.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vapid_private_key
|
str
|
VAPID private key. |
required |
vapid_subject
|
str
|
The |
required |
ttl_seconds
|
int
|
Default TTL for delivered messages. |
60
|
extra_vapid_claims
|
dict[str, str] | None
|
Additional claims merged into the JWT. |
None
|
Source code in tempest_fastapi_sdk/webpush/dispatcher.py
WebPushError
¶
Bases: RuntimeError
Raised when a push delivery attempt fails irrecoverably.
Attributes:
| Name | Type | Description |
|---|---|---|
status_code |
int | None
|
HTTP status returned by the push
service, or |
endpoint |
str | None
|
The subscription endpoint, when known. |
Initialize the error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Human-readable description. |
required |
status_code
|
int | None
|
HTTP status, when known. |
None
|
endpoint
|
str | None
|
Subscription endpoint, when known. |
None
|
Source code in tempest_fastapi_sdk/webpush/dispatcher.py
WebPushGoneError
¶
Bases: WebPushError
Raised when the push service reports the subscription is gone.
Maps to HTTP 404/410. Receivers should delete the subscription from their store and stop attempting delivery.
Source code in tempest_fastapi_sdk/webpush/dispatcher.py
WebPushKeysSchema
¶
Bases: BaseSchema
The keys object returned by PushSubscription.toJSON().
Browsers expose the encryption material as two URL-safe base64 strings; the SDK keeps the wire names so subscriptions can be stored verbatim and replayed on dispatch.
Attributes:
| Name | Type | Description |
|---|---|---|
p256dh |
str
|
Client public ECDH key (URL-safe base64). |
auth |
str
|
Client auth secret (URL-safe base64). |
WebPushPayloadSchema
¶
Bases: BaseSchema
Optional helper for the JSON payload delivered with each push.
Mirrors the Notification API options exposed in service workers; callers that want stricter typing can subclass this for their application-specific event types.
Attributes:
| Name | Type | Description |
|---|---|---|
title |
str | None
|
Notification title shown to the user. |
body |
str | None
|
Notification body. |
icon |
str | None
|
URL of the icon to display. |
badge |
str | None
|
URL of the badge icon (Android). |
tag |
str | None
|
Tag used to coalesce notifications. |
data |
dict[str, Any] | None
|
Arbitrary application payload. |
actions |
list[dict[str, Any]] | None
|
Action button specs. |
WebPushSubscriptionSchema
¶
Bases: BaseSchema
Server-side representation of PushSubscription.toJSON().
Two browser-flavored field names (expirationTime) are exposed
via aliases so the schema round-trips JSON produced by
JSON.stringify(subscription) without manual key mangling.
Attributes:
| Name | Type | Description |
|---|---|---|
endpoint |
str
|
Push service endpoint URL. |
keys |
WebPushKeysSchema
|
Encryption keys. |
expiration_time |
int | None
|
Optional expiration timestamp
in milliseconds since epoch. Aliased to |
WebSocketConnection
dataclass
¶
A single live WebSocket bound to an authenticated user.
Attributes:
| Name | Type | Description |
|---|---|---|
connection_id |
UUID
|
Unique identifier; the hub keys connections by this so the same user can hold several sockets at once (e.g. multi-tab). |
user_id |
UUID
|
The user the connection belongs to. Set by
|
ws |
WebSocket
|
The underlying FastAPI/Starlette socket. |
topics |
set[str]
|
Set of topic strings the connection has
subscribed to. Populated by
:meth: |
WebSocketHub
¶
In-process registry of live WebSocket connections.
Tracks every connection accepted by
:func:tempest_fastapi_sdk.make_websocket_router and offers three
delivery patterns:
send_to(user_id, envelope)— every socket the user has open right now.broadcast(envelope, topic=...)— every subscriber oftopic(or every connection whentopicis omitted).subscribe(connection_id, topic)/unsubscribe(connection_id, topic)— per-connection topic membership.
This hub is single-process. For multi-replica deployments, fan
out across processes via a pub/sub backend (Redis pub/sub,
RabbitMQ topic exchange) — the hub itself only handles
in-process delivery. The future RedisWebSocketHub will swap
the local broadcast for a redis-driven one without changing the
public surface; today, run a single replica or use sticky
sessions when WebSocket fan-out matters.
The hub is safe to share across handlers in the same FastAPI
app — all mutators take an asyncio.Lock so concurrent
register/unregister calls do not corrupt the internal state.
Initialize the hub.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_per_user
|
int
|
Cap on concurrent connections per
user. When the cap is hit on |
5
|
Source code in tempest_fastapi_sdk/websockets/hub.py
WSEnvelope
¶
Bases: BaseSchema
Canonical message envelope for the bundled WebSocket router.
Every frame the SDK sends — application data, heartbeats, errors —
fits this shape so clients can dispatch on type alone. Senders
on the consumer side are encouraged to use it too, but the router
accepts any JSON payload and only the heartbeat frames it owns are
strictly required to follow this schema.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
str
|
Event name. Reserved values:
|
data |
dict[str, Any]
|
Payload — empty dict when none. |
request_id |
str | None
|
Echoes the originating HTTP request-id for end-to-end tracing across SSE/HTTP/WS. |
Banco de dados¶
tempest_fastapi_sdk.db¶
BaseModel
¶
Bases: AsyncAttrs, DeclarativeBase
Abstract base for every SQLAlchemy model in the application.
Every concrete model inherits the four columns required by the
SDK conventions: id (UUID primary key, cross-DB portable),
is_active (soft-delete flag), created_at and updated_at
(timezone-aware timestamps managed by the database).
The class is marked __abstract__ so SQLAlchemy will not try to
map it directly. Concrete subclasses get an auto-generated
__tablename__ from their class name (e.g. UserModel →
user, OrderItemModel → order_item); the Model
suffix is stripped and the remainder is snake-cased. Explicit
__tablename__ declarations still win when set.
Equality and hashing use (type, id) so the same row loaded
across different sessions compares equal — useful in tests and
sets. Unflushed instances (id is None) fall back to Python
identity.
Attributes:
| Name | Type | Description |
|---|---|---|
metadata |
MetaData
|
Configured with :data: |
id |
UUID
|
Primary key. Generated as UUID v4. Uses
|
is_active |
bool
|
Whether the record is active. Defaults
to |
created_at |
datetime
|
Creation timestamp populated by the database on insert. |
updated_at |
datetime
|
Last-update timestamp refreshed by the database on every update. |
to_dict
¶
to_dict(
exclude: list[str] | None = None,
include: dict[str, Any] | None = None,
remove_none: bool = False,
) -> dict[str, Any]
Serialize the row to a plain dict.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exclude
|
list[str] | None
|
Column names to drop from the output. |
None
|
include
|
dict[str, Any] | None
|
Extra entries to merge into the output. |
None
|
remove_none
|
bool
|
Whether to strip keys whose value
is |
False
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: The serialized representation. |
Source code in tempest_fastapi_sdk/db/model.py
update_from_dict
¶
Bulk-assign attributes from a dict.
Unknown keys are silently ignored — only mapped columns of
self.__class__ are eligible. When allowed_fields is
provided, only the intersection with model columns is
applied; this is the recommended way to consume external
payloads (e.g. PATCH bodies) so callers can't write to
sensitive columns like id or role by mistake.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
The payload (typically
|
required |
allowed_fields
|
set[str] | None
|
Whitelist of column
names; only these keys are applied. |
None
|
Source code in tempest_fastapi_sdk/db/model.py
BaseUserModel
¶
Bases: BaseModel
Abstract user table with the columns the admin auth flow needs.
Inherits id/is_active/created_at/updated_at from
:class:BaseModel and adds:
email(unique, indexed) — login identifier. Always stored lowercased; helpers handle the normalization.hashed_password— bcrypt hash produced by :class:tempest_fastapi_sdk.PasswordUtils. Use :meth:set_passwordto write and :meth:check_passwordto verify so the hashing strategy stays consistent across callers.is_admin— gate enforced by the admin auth backend; only users withis_admin=Truemay log in to/admin.last_login_at— populated by the admin login view on every successful authentication.
The class is marked __abstract__ so SQLAlchemy does not try
to map it directly; concrete projects subclass it and either keep
the auto-derived __tablename__ (user) or override it.
Attributes:
| Name | Type | Description |
|---|---|---|
email |
str
|
Login identifier. Unique. 320 chars max (RFC 5321 mailbox limit). |
hashed_password |
str
|
Bcrypt hash; never store plaintext. |
is_admin |
bool
|
Whether the user can access the admin site. |
last_login_at |
datetime | None
|
Last successful login timestamp. |
set_password
¶
Hash plain and write it to :attr:hashed_password.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plain
|
str
|
The plaintext password. |
required |
rounds
|
int
|
bcrypt cost factor. Defaults to |
12
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/db/user_model.py
check_password
¶
Return whether plain matches :attr:hashed_password.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plain
|
str
|
The plaintext password to verify. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/db/user_model.py
normalize_email
staticmethod
¶
Trim whitespace and lowercase value.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
str
|
Raw user input. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The normalized email. |
BaseRepository
¶
BaseRepository(
session: AsyncSession,
*,
model: type[ModelType],
not_found_exception: type[AppException] = NotFoundException,
not_found_message: str | None = None,
create_conflict_message: str | None = None,
update_conflict_message: str | None = None,
bulk_create_conflict_message: str | None = None,
bulk_update_conflict_message: str | None = None,
)
Bases: Generic[ModelType]
Base async repository with generic CRUD operations.
Instantiate directly for plain CRUD (BaseRepository(session,
model=UserModel)) or subclass when adding custom queries — the
subclass forwards model / not_found_exception to
super().__init__ instead of declaring class attributes. The
constructor signature is the contract; there are no magic class
attributes to override.
The default filter logic supports equality on every column plus the following conventions:
name(string) → case-insensitiveILIKE %value%search.boolvalues →.is_(value)(correct SQL boolean check).listvalues →.in_(values)membership.datevalues →func.date(column) == valuewhole-day match.start_in/end_in(date) → range filter against the model'sdatecolumn when present, falling back tocreated_at.
All error messages can be customized per repository instance via
the constructor kwargs (not_found_message,
create_conflict_message, etc.); when omitted, sensible defaults
derived from self.model.__name__ are used.
The same three abstract mappers map_to_schema / map_to_model
/ map_to_response are kept so concrete repositories own the
translation between ORM rows and DTOs.
Attributes:
| Name | Type | Description |
|---|---|---|
model |
type[ModelType]
|
The SQLAlchemy model class operated on. |
not_found_exception |
type[AppException]
|
Exception class raised when single-record lookups miss. |
session |
AsyncSession
|
The async database session. |
Initialize the repository.
Every *_message kwarg is optional — when not provided, the
repository falls back to a generic message derived from the
model class name (e.g. "User not found",
"Conflict creating User").
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
The async database session. |
required |
model
|
type[ModelType]
|
The SQLAlchemy model class this repository operates on. Required. |
required |
not_found_exception
|
type[AppException]
|
Exception class
raised when single-record lookups miss. Defaults to
:class: |
NotFoundException
|
not_found_message
|
str | None
|
Message used when |
None
|
create_conflict_message
|
str | None
|
Message used when
|
None
|
update_conflict_message
|
str | None
|
Message used when
|
None
|
bulk_create_conflict_message
|
str | None
|
Message used
when |
None
|
bulk_update_conflict_message
|
str | None
|
Message used
when |
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
Source code in tempest_fastapi_sdk/db/repository.py
get
async
¶
Return the single record matching filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
The column-value pairs. |
required |
for_update
|
bool
|
Whether to acquire a row-level lock
( |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
The matching row. |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/db/repository.py
get_or_none
async
¶
Return the single record matching filters or None.
Unlike :meth:get, never raises when nothing matches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
The column-value pairs. |
required |
for_update
|
bool
|
Whether to acquire a row-level lock. |
False
|
Returns:
| Type | Description |
|---|---|
ModelType | None
|
ModelType | None: The matching row, or |
Source code in tempest_fastapi_sdk/db/repository.py
get_by_id
async
¶
Return the record with the given primary key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key to look up. |
required |
for_update
|
bool
|
Whether to acquire a row-level lock. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
The matching row. |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/db/repository.py
exists
async
¶
Return whether at least one row matches filters.
Executes a SELECT 1 ... LIMIT 1 so the row is never
fully loaded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
The filter conditions. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Source code in tempest_fastapi_sdk/db/repository.py
first
async
¶
first(
filters: dict[str, Any] | None = None,
order_by: Any | None = None,
ascending: bool = True,
) -> ModelType | None
Return the first matching row or None.
Convenience wrapper around :meth:list for cases that only
need one row but want to control ordering.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
The filter conditions. |
None
|
order_by
|
Any | None
|
A SQLAlchemy column expression to order by.
|
None
|
ascending
|
bool
|
Whether to order ascending. |
True
|
Returns:
| Type | Description |
|---|---|
ModelType | None
|
ModelType | None: The first matching row, or |
Source code in tempest_fastapi_sdk/db/repository.py
list
async
¶
list(
filters: dict[str, Any] | None = None,
order_by: Any | None = None,
ascending: bool = True,
) -> list[ModelType]
Return every record matching filters.
Returns [] (never raises) when nothing matches, in line
with the SDK collection convention.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
The filter conditions. |
None
|
order_by
|
Any | None
|
A SQLAlchemy column expression (e.g.
|
None
|
ascending
|
bool
|
Whether to order ascending. Ignored
when |
True
|
Returns:
| Type | Description |
|---|---|
list[ModelType]
|
list[ModelType]: The matching rows. |
Source code in tempest_fastapi_sdk/db/repository.py
paginate
async
¶
paginate(
filters: dict[str, Any] | None = None,
order_by: str | None = None,
page: int = 1,
page_size: int = 20,
ascending: bool = True,
query: Select[Any] | None = None,
) -> dict[str, Any]
Return a single page of records matching filters.
When order_by is None, falls back to
self.model.created_at.desc(). The total count is computed
from the same filtered (and possibly joined) query, so custom
queries with joins still report a correct total.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
Filter conditions. |
None
|
order_by
|
str | None
|
Column name to order by, or
|
None
|
page
|
int
|
The 1-indexed page number. |
1
|
page_size
|
int
|
The number of items per page. |
20
|
ascending
|
bool
|
Whether to order ascending. Ignored
when |
True
|
query
|
Select[Any] | None
|
A pre-built |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: A mapping with keys |
dict[str, Any]
|
|
Source code in tempest_fastapi_sdk/db/repository.py
cursor_paginate
async
¶
cursor_paginate(
filters: dict[str, Any] | None = None,
cursor: str | None = None,
limit: int = 20,
order_by: str = "created_at",
ascending: bool = False,
) -> dict[str, Any]
Return a single cursor-paginated page of records.
Cursor pagination orders by (order_by, id) so the result
is stable under concurrent inserts and scales without a
COUNT(*). The cursor encodes the last row's
(order_by_value, id) so the next page can continue
precisely past it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
Filter conditions. |
None
|
cursor
|
str | None
|
Opaque cursor from the previous page;
|
None
|
limit
|
int
|
Maximum items to return in this page. |
20
|
order_by
|
str
|
Column to sort by. Must exist on the model. |
'created_at'
|
ascending
|
bool
|
Whether to sort ascending. Defaults to
|
False
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Mapping with |
dict[str, Any]
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
When |
Source code in tempest_fastapi_sdk/db/repository.py
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 | |
add
async
¶
Insert model into the database.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ModelType
|
The instance to insert. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
The same instance after |
ModelType
|
|
Raises:
| Type | Description |
|---|---|
ConflictException
|
On integrity violations (unique constraint, FK error, etc.). |
Source code in tempest_fastapi_sdk/db/repository.py
add_all
async
¶
Insert several models in a single transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
list[ModelType]
|
The instances to insert. |
required |
Returns:
| Type | Description |
|---|---|
List[ModelType]
|
list[ModelType]: The same list after every instance is |
List[ModelType]
|
refreshed. |
Raises:
| Type | Description |
|---|---|
ConflictException
|
On integrity violations. |
Source code in tempest_fastapi_sdk/db/repository.py
update
async
¶
Persist mutations made on an attached model.
The instance must already be tracked by the session (e.g.
returned by :meth:get) with its fields modified. This
method only commits and refreshes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
ModelType
|
The mutated instance. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
The same instance after |
Raises:
| Type | Description |
|---|---|
ConflictException
|
On integrity violations. |
Source code in tempest_fastapi_sdk/db/repository.py
update_many
async
¶
Commit several mutated instances in a single transaction.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
models
|
list[ModelType]
|
The mutated instances. |
required |
Returns:
| Type | Description |
|---|---|
List[ModelType]
|
list[ModelType]: The same list. |
Raises:
| Type | Description |
|---|---|
ConflictException
|
On integrity violations. |
Source code in tempest_fastapi_sdk/db/repository.py
bulk_update
async
¶
Issue a single UPDATE ... WHERE against the table.
Bypasses the unit-of-work entirely — useful for mass mutations that don't need to refresh each affected row in the session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
Filter conditions identifying the rows to mutate. An empty mapping is rejected to prevent accidental table-wide updates. |
required |
values
|
dict[str, Any]
|
Column-value pairs to set on the matching rows. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The number of rows affected. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
ConflictException
|
On integrity violations. |
Source code in tempest_fastapi_sdk/db/repository.py
bulk_create_values
async
¶
Insert many rows in a single INSERT ... VALUES (...), (...) statement.
Unlike :meth:add_all, this bypasses the unit-of-work — the
rows are not refreshed nor attached to the session. Use when
you have a large batch (≥ 50 rows) and don't need the ORM
instances back; the round-trip count drops from N to 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
list[dict[str, Any]]
|
One mapping per row, keyed by column name (not attribute name; usually they match for SDK models). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Number of rows inserted ( |
Raises:
| Type | Description |
|---|---|
ConflictException
|
On unique / FK violations. |
ValueError
|
When |
Source code in tempest_fastapi_sdk/db/repository.py
bulk_upsert
async
¶
bulk_upsert(
rows: List[dict[str, Any]],
*,
conflict_columns: List[str],
update_columns: List[str] | None = None,
) -> int
Issue an INSERT ... ON CONFLICT DO UPDATE in one round-trip.
Picks the dialect-specific upsert syntax automatically —
Postgres (postgresql.insert) and SQLite
(sqlite.insert) are supported. Other dialects raise
:class:NotImplementedError so the caller can fall back to
a transactional SELECT FOR UPDATE loop.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rows
|
list[dict[str, Any]]
|
One mapping per row. |
required |
conflict_columns
|
list[str]
|
The columns whose
conflict triggers the |
required |
update_columns
|
list[str] | None
|
Columns to refresh
on conflict. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Total rows touched (inserted + updated). |
Raises:
| Type | Description |
|---|---|
ConflictException
|
On non-recoverable integrity errors. |
NotImplementedError
|
When the active SQLAlchemy dialect has no native upsert. |
ValueError
|
When |
Source code in tempest_fastapi_sdk/db/repository.py
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 | |
delete
async
¶
Delete a single row by its primary key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/db/repository.py
delete_many
async
¶
Delete every row matching filters.
An empty filters dict deletes every row in the table.
Callers must opt in explicitly — the behavior is intentional.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
The conditions identifying the rows to delete. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The number of rows deleted. |
Source code in tempest_fastapi_sdk/db/repository.py
delete_batch
async
¶
Delete several rows by primary key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ids
|
list[UUID]
|
The primary keys to delete. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The number of rows deleted. |
Source code in tempest_fastapi_sdk/db/repository.py
soft_delete
async
¶
Soft-delete a row by setting is_active=False.
Loads the row, flips is_active, persists. Returns the
refreshed instance so callers can inspect the post-state.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
The row with |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/db/repository.py
restore
async
¶
Reactivate a soft-deleted row by setting is_active=True.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
The row with |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/db/repository.py
count
async
¶
Count the rows matching filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
The filter conditions. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The matching row count. |
Source code in tempest_fastapi_sdk/db/repository.py
map_to_schema
¶
Map an ORM row to its schema/domain representation.
Concrete repositories MUST implement this to bridge the data layer and the rest of the application.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
ModelType
|
The ORM row to convert. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The schema/domain object. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Always — subclasses must override. |
Source code in tempest_fastapi_sdk/db/repository.py
map_to_model
¶
Build an ORM instance from a plain dict payload.
Default implementation constructs self.model(**data);
override for custom field mapping.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
The payload. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ModelType |
ModelType
|
A new (unpersisted) ORM instance. |
Source code in tempest_fastapi_sdk/db/repository.py
map_to_response
¶
Map an ORM row to its API response schema.
Concrete repositories MUST implement this when used from the router layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
instance
|
ModelType
|
The ORM row to convert. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The response schema. |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
Always — subclasses must override. |
Source code in tempest_fastapi_sdk/db/repository.py
SoftDeleteMixin
¶
Add a deleted_at timestamp for non-destructive deletes.
Pairs with the canonical is_active flag on
:class:tempest_fastapi_sdk.BaseModel: is_active toggles
visibility quickly while deleted_at records when the soft
delete happened (useful for audit and retention policies).
A row is considered "alive" when deleted_at IS NULL. Filtering
is the caller's responsibility — the mixin keeps the column
declarative-only so it composes with arbitrary query strategies
(global filters, partial indexes, repository hooks).
Attributes:
| Name | Type | Description |
|---|---|---|
deleted_at |
datetime | None
|
Timestamp of the soft delete,
or |
AuditMixin
¶
Add created_by / updated_by foreign-key columns.
Tracks which user (by UUID) last touched a row. The mixin only declares the columns — populating them is the application's responsibility, typically inside the service layer (where the current user is in scope) right before calling the repository.
Attributes:
| Name | Type | Description |
|---|---|---|
created_by |
UUID | None
|
UUID of the user that created the row. Nullable for system-generated rows. |
updated_by |
UUID | None
|
UUID of the user that last updated the row. Nullable until the first update. |
stamp_created_by
¶
Set both audit columns to user_id on initial insert.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
The acting user's primary key. |
required |
stamp_updated_by
¶
Update updated_by to user_id ahead of an UPDATE.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
The acting user's primary key. |
required |
AsyncDatabaseManager
¶
AsyncDatabaseManager(
db_url: str,
*,
echo: bool = False,
pool_size: int = 10,
max_overflow: int = 20,
pool_recycle: int = 3600,
connect_args: dict[str, Any] | None = None,
poolclass: type[Pool] | None = None,
**engine_kwargs: Any,
)
Manage the async SQLAlchemy engine and session lifecycle.
Handles engine creation tailored to the database backend (SQLite
gets check_same_thread=False by default, everything else
gets a pooled config), session factory construction, and table
create/drop helpers. Designed to be instantiated once per
application and reused across requests.
Backend detection uses sqlalchemy.engine.make_url so URLs
like sqlite+aiosqlite://... are matched precisely without
relying on substring tricks.
Attributes:
| Name | Type | Description |
|---|---|---|
is_sqlite |
bool
|
Whether the URL targets a SQLite backend. |
The connection URL itself is stored on a private attribute so it
never leaks through repr() or accidental logging. Use the
:attr:db_url_safe property when a redacted form is needed.
Initialize the manager (does not open connections yet).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db_url
|
str
|
The database connection URL. |
required |
echo
|
bool
|
Whether to emit SQL to stdout. |
False
|
pool_size
|
int
|
Number of permanent connections in the pool. Ignored for SQLite URLs. |
10
|
max_overflow
|
int
|
Extra connections allowed above the pool size. Ignored for SQLite URLs. |
20
|
pool_recycle
|
int
|
Recycle connections older than this many seconds. Ignored for SQLite URLs. |
3600
|
connect_args
|
dict[str, Any] | None
|
Driver-level
arguments forwarded to |
None
|
poolclass
|
type[Pool] | None
|
Override SQLAlchemy's
default pool class. Useful for tests
( |
None
|
**engine_kwargs
|
Any
|
Any additional keyword arguments are
passed through to |
{}
|
Source code in tempest_fastapi_sdk/db/connection.py
db_url_safe
property
¶
Return the URL with credentials masked.
Useful for diagnostics, health payloads or log lines —
postgresql+asyncpg://user:pass@host/db becomes
postgresql+asyncpg://***@host/db.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The URL safe to surface outside the manager. |
is_connected
property
¶
Whether the engine is currently initialized.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
bool
|
meth: |
connect
async
¶
Create the engine and session factory if they don't exist.
Idempotent — calling twice is a no-op.
Source code in tempest_fastapi_sdk/db/connection.py
disconnect
async
¶
Dispose the engine and clear the session factory.
Safe to call multiple times.
Source code in tempest_fastapi_sdk/db/connection.py
get_session
async
¶
Return a new AsyncSession bound to the engine.
Lazy-connects on first use. The caller is responsible for
closing the session (use :meth:get_session_context for
managed lifecycle).
Returns:
| Name | Type | Description |
|---|---|---|
AsyncSession |
AsyncSession
|
A new session. |
Source code in tempest_fastapi_sdk/db/connection.py
get_session_context
async
¶
Yield a session that auto-commits on exit and rolls back on error.
Yields:
| Name | Type | Description |
|---|---|---|
AsyncSession |
AsyncGenerator[AsyncSession]
|
A managed session. |
Raises:
| Type | Description |
|---|---|
Exception
|
Re-raises whatever the caller raised inside
the |
Source code in tempest_fastapi_sdk/db/connection.py
session_dependency
async
¶
FastAPI dependency yielding one session per request.
Use as Depends(db.session_dependency). Differs from
:meth:get_session_context in that it does not commit on
success — commits are the responsibility of the service /
repository layer. The session is closed when the request
scope ends; failures bubble up unchanged.
Yields:
| Name | Type | Description |
|---|---|---|
AsyncSession |
AsyncGenerator[AsyncSession]
|
A request-scoped session. |
Source code in tempest_fastapi_sdk/db/connection.py
health_check
async
¶
Return whether a trivial SELECT 1 succeeds.
Suitable for /health endpoints. Swallows every exception
and returns False so callers can branch on the result
without dealing with driver-specific error types.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
bool
|
|
Source code in tempest_fastapi_sdk/db/connection.py
create_tables
async
¶
Issue CREATE TABLE for every model registered on BaseModel.
Intended for tests and local development. Production schemas
should be managed by Alembic (see
:class:tempest_fastapi_sdk.db.migrations.AlembicHelper).
Source code in tempest_fastapi_sdk/db/connection.py
drop_tables
async
¶
Issue DROP TABLE for every model registered on BaseModel.
Intended for tests and local development.
Source code in tempest_fastapi_sdk/db/connection.py
AlembicHelper
¶
High-level wrapper around the Alembic command surface.
Encapsulates a single alembic.ini configuration and exposes
the operations that matter for day-to-day work — upgrade,
downgrade, revision authoring, schema-vs-models check — without
leaking Alembic internals into application code.
All methods are synchronous because Alembic itself is sync; run
them from CLI scripts or from FastAPI's startup hook via
asyncio.to_thread if you must call them from async code.
Attributes:
| Name | Type | Description |
|---|---|---|
config_path |
str
|
Path to the |
Initialize the helper.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
str
|
Path to |
'alembic.ini'
|
db_url
|
str | None
|
If provided, overrides
|
None
|
Source code in tempest_fastapi_sdk/db/migrations.py
config
property
¶
Return a fresh :class:alembic.config.Config instance.
A new instance is built on every access so the helper stays stateless and safe to share across threads — Alembic mutates the config object during command execution.
sqlalchemy.url resolution order:
db_urlpassed on the constructor (explicit override).- The value already on the ini file.
- The
DATABASE_URLenvironment variable (loaded from.envbefore invoking the helper). src.core.settings.settings.DATABASE_URLwhen the scaffolded layout is detected.
The SDK-generated ini ships with sqlalchemy.url = empty
on purpose so secrets never enter version control — the
resolution chain above fills it at runtime.
Returns:
| Name | Type | Description |
|---|---|---|
Config |
Config
|
The configured Alembic config. |
init
¶
init(
directory: str = "alembic",
*,
metadata_module: str | None = None,
metadata_attr: str = "BaseModel",
db_url: str = "sqlite+aiosqlite:///./app.db",
) -> None
Scaffold a new Alembic environment in directory.
Wraps alembic init -t async and then overwrites the
generated env.py with the SDK's template, which already
wires the metadata import, sets compare_type /
compare_server_default and enables batch mode for SQLite.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
str
|
Target directory for |
'alembic'
|
metadata_module
|
str | None
|
Dotted module path that
exposes the SQLAlchemy metadata (e.g. |
None
|
metadata_attr
|
str
|
Name of the attribute inside
|
'BaseModel'
|
db_url
|
str
|
Value to write under |
'sqlite+aiosqlite:///./app.db'
|
Source code in tempest_fastapi_sdk/db/migrations.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | |
upgrade
¶
Apply migrations up to revision (default: head).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
revision
|
str
|
Target revision identifier or relative
spec ( |
'head'
|
Source code in tempest_fastapi_sdk/db/migrations.py
downgrade
¶
Revert migrations down to revision (default: one step back).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
revision
|
str
|
Target revision identifier or relative
spec. |
'-1'
|
Source code in tempest_fastapi_sdk/db/migrations.py
current
¶
Return the revision the database is currently stamped at.
Reads alembic_version via a temporary sync engine derived
from the configured URL (async drivers get stripped).
Returns:
| Type | Description |
|---|---|
str | None
|
str | None: The revision identifier, or |
str | None
|
|
Source code in tempest_fastapi_sdk/db/migrations.py
heads
¶
Return every head revision known to the script directory.
Multiple heads indicate divergent branches in the migration graph — usually a sign that a merge migration is needed.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: The head revision identifiers. |
Source code in tempest_fastapi_sdk/db/migrations.py
history
¶
Return the migration history as a printable string.
Wraps alembic history and captures stdout so the result
can be logged or returned from an admin endpoint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
verbose
|
bool
|
Forward |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The captured output. |
Source code in tempest_fastapi_sdk/db/migrations.py
revision
¶
revision(
message: str, *, autogenerate: bool = True, sql: bool = False, head: str = "head"
) -> Any
Create a new revision file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
Description of the change (becomes the migration slug). |
required |
autogenerate
|
bool
|
Run autogenerate against the live
schema. When |
True
|
sql
|
bool
|
Emit SQL to stdout instead of executing. |
False
|
head
|
str
|
Parent revision; defaults to the current head. |
'head'
|
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The Alembic |
Any
|
by the command, as returned by |
Source code in tempest_fastapi_sdk/db/migrations.py
stamp
¶
Stamp the database with revision without running migrations.
Useful when importing an existing schema into Alembic for the first time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
revision
|
str
|
The revision to stamp. |
'head'
|
Source code in tempest_fastapi_sdk/db/migrations.py
check
¶
Return True if no autogenerate diff would be produced.
Wraps alembic check (added in Alembic 1.9). Suitable for
CI to fail when models drift from the migration tree.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Source code in tempest_fastapi_sdk/db/migrations.py
show
¶
Return the details of a single revision.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
revision
|
str
|
The revision to inspect. |
'head'
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Multi-line description (id, parent, doc, path). |
Source code in tempest_fastapi_sdk/db/migrations.py
Schemas¶
tempest_fastapi_sdk.schemas¶
BaseSchema
¶
Bases: BaseModel
Base class for every Pydantic schema in an application.
Centralizes the configuration that all DTOs share: ignore extra fields, allow building schemas from ORM attributes, serialize enum values, strip whitespace from strings, and validate assignments after construction.
Attributes:
| Name | Type | Description |
|---|---|---|
model_config |
ConfigDict
|
The Pydantic configuration. |
to_dict
¶
to_dict(
exclude: list[str] | None = None, include: dict[str, Any] | None = None
) -> dict[str, Any]
Serialize the schema to a plain dict.
Drops None values, removes keys listed in exclude
and merges include on top of the remaining payload.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exclude
|
list[str] | None
|
Field names to drop from the output dictionary. |
None
|
include
|
dict[str, Any] | None
|
Extra entries to merge into the output (override existing keys). |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: The serialized representation. |
Source code in tempest_fastapi_sdk/schemas/base.py
to_json
¶
Serialize the schema to a JSON string.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The JSON encoded representation of the schema. |
BaseResponseSchema
¶
Bases: BaseSchema
Response schema with the four columns every ORM record carries.
Used as the parent of any *ResponseSchema whose payload mirrors
a row from a table inheriting from
:class:tempest_fastapi_sdk.db.model.BaseModel. created_at and
updated_at are normalized to UTC after validation so the API
always emits timezone-aware timestamps regardless of how the DB
driver returned them.
Attributes:
| Name | Type | Description |
|---|---|---|
id |
UUID
|
The unique identifier of the record. |
is_active |
bool
|
Whether the record is active (soft-delete convention). |
created_at |
datetime
|
The creation timestamp, normalized to UTC. |
updated_at |
datetime
|
The last update timestamp, normalized to UTC. |
BasePaginationFilterSchema
¶
Bases: BaseSchema
Base filter schema for paginated list endpoints.
Subclass it to add domain-specific filter fields. The base
get_conditions method returns every populated field except
the pagination/sort keys, which is the contract expected by
:class:tempest_fastapi_sdk.db.repository.BaseRepository.paginate.
Field names and defaults mirror the BaseRepository.paginate
keyword arguments so passing the schema straight through works
without renaming:
.. code-block:: python
result = await repo.paginate(
filters=f.get_conditions(),
order_by=f.order_by,
page=f.page,
page_size=f.page_size,
ascending=f.ascending,
)
Attributes:
| Name | Type | Description |
|---|---|---|
page |
int
|
The page number to retrieve (1-indexed). |
page_size |
int
|
The number of items per page. |
order_by |
str | None
|
The column name to order by. |
ascending |
bool
|
Whether to order ascending. Ignored when
|
is_active |
bool | None
|
Filter by active status. |
get_conditions
¶
Return the dict of filter conditions for the repository.
Strips the pagination and sort keys so the resulting mapping
contains only domain-level filters consumable by
:meth:BaseRepository.paginate.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: The dictionary of filter conditions. |
Source code in tempest_fastapi_sdk/schemas/pagination.py
BasePaginationSchema
¶
Bases: BaseSchema, Generic[T]
Generic envelope returned by paginated endpoints.
Wraps the page of items together with the pagination metadata
the frontend needs to render controls. Field names match the
request-side :class:BasePaginationFilterSchema and the
repository keyword arguments, so the round-trip stays free of
renames.
Attributes:
| Name | Type | Description |
|---|---|---|
items |
list[T]
|
The items in the current page. |
total |
int
|
The total number of items across all pages. |
page |
int
|
The current page number (1-indexed). |
page_size |
int
|
The number of items per page. |
pages |
int
|
The total number of pages. |
CursorPaginationFilterSchema
¶
Bases: BaseSchema
Request filter for cursor-based pagination endpoints.
Cursor pagination scales better than offset pagination on large
tables (no COUNT(*), stable under concurrent inserts) at the
cost of losing random-access semantics. Subclass to add domain
filters; :meth:get_conditions strips the cursor/sort keys
automatically.
Attributes:
| Name | Type | Description |
|---|---|---|
cursor |
str | None
|
Opaque cursor returned by the previous
page. |
limit |
int
|
Maximum number of items to return. |
order_by |
str
|
Column to sort by. Must be a sortable column
with a stable secondary tie-break ( |
ascending |
bool
|
Whether to sort ascending. Defaults to
|
get_conditions
¶
Return only the domain-level filter conditions.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: The filters with pagination/sort keys |
dict[str, Any]
|
stripped. |
Source code in tempest_fastapi_sdk/schemas/pagination.py
CursorPaginationSchema
¶
Bases: BaseSchema, Generic[T]
Generic envelope returned by cursor-paginated endpoints.
Attributes:
| Name | Type | Description |
|---|---|---|
items |
list[T]
|
The items in the current page. |
next_cursor |
str | None
|
Cursor to request the next page,
or |
has_more |
bool
|
Whether another page is available. |
limit |
int
|
The page size used to produce this payload. |
LogEntrySchema
¶
Bases: BaseSchema
A single structured log record parsed from a JSON log file.
The SDK's :class:tempest_fastapi_sdk.JSONFormatter writes one JSON
object per line. This schema mirrors its core fields and accepts any
additional extra={...} keys (e.g. path, request_id,
http_500) via extra="allow" so nothing is silently dropped
by the /logs endpoint.
Attributes:
| Name | Type | Description |
|---|---|---|
timestamp |
str
|
ISO-8601 UTC timestamp ( |
level |
str
|
Log level name ( |
logger |
str
|
Name of the logger that emitted the record. |
message |
str
|
The formatted log message. |
request_id |
str | None
|
Correlation ID when present. |
exception |
str | None
|
Formatted traceback when the record
carried |
Services & Controllers¶
BaseService
¶
Bases: Generic[RepositoryT, ResponseT]
Thin business-logic layer wrapping a :class:BaseRepository.
The default implementation exposes CRUD pass-through methods that
delegate to the repository and apply map_to_response so the
surface matches what routers/controllers consume. Concrete
services should override methods that involve orchestration
(multi-repository writes, external side effects, domain rules);
pure pass-through methods can be left untouched.
Generic parameters
RepositoryT: The concrete repository class. ResponseT: The response schema returned by the service.
Attributes:
| Name | Type | Description |
|---|---|---|
repository |
RepositoryT
|
The repository the service delegates to. |
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
repository
|
RepositoryT
|
The repository to delegate to. |
required |
Source code in tempest_fastapi_sdk/services/base.py
get_by_id
async
¶
Fetch a single record by primary key and map it to a response.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ResponseT |
ResponseT
|
The mapped response. |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/services/base.py
get_or_none
async
¶
Return the matching record (mapped) or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
Filter conditions. |
required |
Returns:
| Type | Description |
|---|---|
ResponseT | None
|
ResponseT | None: The mapped response, or |
Source code in tempest_fastapi_sdk/services/base.py
list
async
¶
list(
filters: dict[str, Any] | None = None,
order_by: Any | None = None,
ascending: bool = True,
) -> list[ResponseT]
Return every matching record mapped to a response.
Returns [] when nothing matches, in line with the SDK
collection convention (never raises *NotFoundError for
empty result sets).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
Filter conditions. |
None
|
order_by
|
Any | None
|
A SQLAlchemy column expression to order by. |
None
|
ascending
|
bool
|
Whether to order ascending. |
True
|
Returns:
| Type | Description |
|---|---|
list[ResponseT]
|
list[ResponseT]: The mapped responses. |
Source code in tempest_fastapi_sdk/services/base.py
paginate
async
¶
paginate(
filters: dict[str, Any] | None = None,
order_by: str | None = None,
page: int = 1,
page_size: int = 20,
ascending: bool = True,
) -> dict[str, Any]
Return an offset page with items mapped to responses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
Filter conditions. |
None
|
order_by
|
str | None
|
Column name to order by. |
None
|
page
|
int
|
1-indexed page number. |
1
|
page_size
|
int
|
Items per page. |
20
|
ascending
|
bool
|
Whether to order ascending. |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Mapping with |
dict[str, Any]
|
|
Source code in tempest_fastapi_sdk/services/base.py
count
async
¶
Count the rows matching filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
The filter conditions. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The matching row count. |
Source code in tempest_fastapi_sdk/services/base.py
exists
async
¶
Whether at least one row matches filters.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any]
|
Filter conditions. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Source code in tempest_fastapi_sdk/services/base.py
delete
async
¶
Delete a row by primary key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Raises:
| Type | Description |
|---|---|
AppException
|
|
Source code in tempest_fastapi_sdk/services/base.py
BaseController
¶
Bases: Generic[ServiceT, ResponseT]
Thin orchestration layer between routers and services.
Following the SDK layering rules (router → controller → service → repository), controllers are kept present even when no orchestration is required so the import graph stays uniform. Override methods here when a single endpoint needs to call multiple services or apply cross-cutting policy; leave the pass-throughs untouched otherwise.
Generic parameters
ServiceT: The concrete service class. ResponseT: The response schema returned to the router.
Attributes:
| Name | Type | Description |
|---|---|---|
service |
ServiceT
|
The service the controller delegates to. |
Initialize the controller.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
ServiceT
|
The service to delegate to. |
required |
Source code in tempest_fastapi_sdk/controllers/base.py
get_by_id
async
¶
Pass-through to :meth:BaseService.get_by_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ResponseT |
ResponseT
|
The mapped response. |
Source code in tempest_fastapi_sdk/controllers/base.py
list
async
¶
list(
filters: dict[str, Any] | None = None,
order_by: Any | None = None,
ascending: bool = True,
) -> list[ResponseT]
Pass-through to :meth:BaseService.list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
Filter conditions. |
None
|
order_by
|
Any | None
|
A SQLAlchemy column expression. |
None
|
ascending
|
bool
|
Whether to order ascending. |
True
|
Returns:
| Type | Description |
|---|---|
list[ResponseT]
|
list[ResponseT]: The mapped responses. |
Source code in tempest_fastapi_sdk/controllers/base.py
paginate
async
¶
paginate(
filters: dict[str, Any] | None = None,
order_by: str | None = None,
page: int = 1,
page_size: int = 20,
ascending: bool = True,
) -> dict[str, Any]
Pass-through to :meth:BaseService.paginate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
Filter conditions. |
None
|
order_by
|
str | None
|
Column name to order by. |
None
|
page
|
int
|
1-indexed page number. |
1
|
page_size
|
int
|
Items per page. |
20
|
ascending
|
bool
|
Whether to order ascending. |
True
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: The paginated payload. |
Source code in tempest_fastapi_sdk/controllers/base.py
count
async
¶
Pass-through to :meth:BaseService.count.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filters
|
dict[str, Any] | None
|
The filter conditions. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The matching row count. |
Source code in tempest_fastapi_sdk/controllers/base.py
delete
async
¶
Pass-through to :meth:BaseService.delete.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
id
|
UUID
|
The primary key. |
required |
Exceções¶
tempest_fastapi_sdk.exceptions¶
AppException
¶
AppException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: HTTPException
Base exception for all application-level errors.
Concrete projects raise either a domain-specific subclass (kept
around for except DomainError matching) or the base directly,
passing code / status_code / message via constructor
keyword arguments. Class-level attributes are the defaults each
constructor argument falls back to, never required overrides::
class UserNotFoundError(NotFoundException):
"""Subclass exists only for isinstance/except matching."""
raise UserNotFoundError(
"Usuário não encontrado",
code="USER_NOT_FOUND",
details={"email": email},
)
The matching exception handler (see
:mod:tempest_fastapi_sdk.api.handlers) emits the JSON shape::
{
"detail": "<message>",
"code": "<code>",
"details": {"<any>": "<context>"}
}
Class attributes (defaults the constructor falls back to): status_code (int): HTTP status code. message (str): Default human-readable message. code (str): Stable, machine-readable identifier.
Instance attributes
status_code (int): The status code attached to this instance. code (str): The error code attached to this instance. details (dict[str, Any]): Free-form context attached to the response payload.
Initialize the exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str | None
|
Override the class-level message. |
None
|
code
|
str | None
|
Override the class-level error code on this instance only — leaves other instances of the same class untouched. |
None
|
status_code
|
int | None
|
Override the class-level HTTP status code on this instance only. |
None
|
details
|
dict[str, Any] | None
|
Structured context to attach to the JSON response. |
None
|
headers
|
dict[str, str] | None
|
Optional HTTP headers to include in the response. |
None
|
Source code in tempest_fastapi_sdk/exceptions/base.py
NotFoundException
¶
NotFoundException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when a single resource cannot be located.
Use for get_by_id / get_by_email style lookups. NEVER use
for collection endpoints — those should return [] instead.
Source code in tempest_fastapi_sdk/exceptions/base.py
ConflictException
¶
ConflictException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when a write would violate a uniqueness/integrity rule.
Typically surfaced by the repository when SQLAlchemy raises an
IntegrityError on insert/update.
Source code in tempest_fastapi_sdk/exceptions/base.py
UnauthorizedException
¶
UnauthorizedException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when the caller is not authenticated.
Use for missing/invalid/expired credentials. For "authenticated
but not allowed" cases, use
:class:tempest_fastapi_sdk.exceptions.forbidden.ForbiddenException.
Source code in tempest_fastapi_sdk/exceptions/base.py
ForbiddenException
¶
ForbiddenException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when the caller is authenticated but lacks permission.
Source code in tempest_fastapi_sdk/exceptions/base.py
ValidationException
¶
ValidationException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when input fails a business rule beyond Pydantic.
Pydantic emits 422 automatically for schema validation; use this for downstream rules that only the service layer can enforce.
Source code in tempest_fastapi_sdk/exceptions/base.py
TooManyRequestsException
¶
TooManyRequestsException(
message: str | None = None,
*,
retry_after_seconds: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when a client exceeds a rate limit or attempt budget.
Carries an optional Retry-After header (seconds) and mirrors
the same value under details["retry_after_seconds"] so clients
can back off without parsing headers. Used by
:class:tempest_fastapi_sdk.utils.AttemptThrottle and suitable for
any throttled flow (login, OTP, code verification).
Initialize the exception.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str | None
|
Override the class-level message. |
None
|
retry_after_seconds
|
int | None
|
Cooldown in seconds. When
given, sets the |
None
|
details
|
dict[str, Any] | None
|
Structured context. |
None
|
headers
|
dict[str, str] | None
|
Extra response headers;
merged with the |
None
|
Source code in tempest_fastapi_sdk/exceptions/too_many_requests.py
InvalidTokenException
¶
InvalidTokenException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: UnauthorizedException
Raised when a JWT fails signature or claim validation.
Source code in tempest_fastapi_sdk/exceptions/base.py
ExpiredTokenException
¶
ExpiredTokenException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: UnauthorizedException
Raised when a JWT's exp claim is in the past.
Source code in tempest_fastapi_sdk/exceptions/base.py
FileTooLargeException
¶
FileTooLargeException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when an uploaded file exceeds the configured size limit.
Source code in tempest_fastapi_sdk/exceptions/base.py
InvalidFileTypeException
¶
InvalidFileTypeException(
message: str | None = None,
*,
code: str | None = None,
status_code: int | None = None,
details: dict[str, Any] | None = None,
headers: dict[str, str] | None = None,
)
Bases: AppException
Raised when an uploaded file's extension or MIME is not allowed.
Source code in tempest_fastapi_sdk/exceptions/base.py
API (integração FastAPI)¶
tempest_fastapi_sdk.api¶
register_exception_handlers
¶
register_exception_handlers(
app: FastAPI,
*,
log_traceback: bool = True,
include_traceback: bool = False,
log_level: int = ERROR,
) -> None
Register the SDK's exception handlers on a FastAPI app.
Wires three handlers, in order of specificity:
- :class:
AppException→ :func:app_exception_handler. Every domain-specific subclass returned by routers, services and repositories is serialized consistently. - :class:
starlette.exceptions.HTTPException→ :func:make_http_exception_handlerfactory.raise HTTPException(500, ...)would otherwise bypass the SDK's catch-all (Starlette intercepts HTTPException inside its own middleware), so this handler restores the log + envelope behavior for 5xx HTTPExceptions while leaving 4xx untouched. - :class:
Exception(catch-all) → traceback logger + generic 500 envelope. Without this, FastAPI's default returns the string"Internal Server Error"with no log entry beyond the access line, leaving operators blind to real failures.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
FastAPI
|
The FastAPI application to wire. |
required |
log_traceback
|
bool
|
Whether the 5xx handlers attach the
full traceback to the log record. Defaults to |
True
|
include_traceback
|
bool
|
When |
False
|
log_level
|
int
|
Logging level used by the 5xx handlers.
Defaults to :data: |
ERROR
|
Source code in tempest_fastapi_sdk/api/handlers.py
make_app_exception_handler
¶
Build the handler for :class:AppException subclasses.
Serializes the exception to the SDK envelope and emits an
INFO-level log line (no traceback — 4xx is normal client
flow). 5xx AppException subclasses bump up to
log_level with a traceback and the
:data:HTTP_500_MARKER flag so 500.log captures them.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_level
|
int
|
Level used only for 5xx |
INFO
|
Returns:
| Name | Type | Description |
|---|---|---|
AppExceptionHandler |
AppExceptionHandler
|
An async |
AppExceptionHandler
|
callable. |
Source code in tempest_fastapi_sdk/api/handlers.py
make_http_exception_handler
¶
make_http_exception_handler(
*, log_traceback: bool = True, log_level: int = ERROR
) -> HTTPExceptionHandler
Build the handler for raw :class:starlette.exceptions.HTTPException.
Without this, raise HTTPException(500, "...") (or 404,
403, …) bypasses the SDK's Exception catch-all entirely:
Starlette intercepts HTTPException instances inside its
ExceptionMiddleware and routes them to its own default — a
bare JSONResponse({"detail": exc.detail}) with no log entry.
Operators see the 500 in the access log and no trace.
This handler closes that gap for 5xx HTTPExceptions:
- Whenever
exc.status_code >= 500, the failure is logged atlog_level(ERROR by default) undertempest_fastapi_sdk.api.handlers. The record is flagged with :data:HTTP_500_MARKERsoconfigure_logging(log_dir=…)routes it to the dedicated500.logalongside the trace. - The response keeps the original
status_code/headersand adds the SDK envelope shape (detail/code/details), so frontends consuming the same envelope across :class:AppExceptionand rawHTTPExceptiondon't need to branch.
4xx HTTPExceptions are returned untouched (Starlette's default behavior), since those represent normal client-side outcomes that don't deserve a stack trace.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_traceback
|
bool
|
Whether to attach |
True
|
log_level
|
int
|
Logging level used for 5xx records. |
ERROR
|
Returns:
| Name | Type | Description |
|---|---|---|
HTTPExceptionHandler |
HTTPExceptionHandler
|
An async |
HTTPExceptionHandler
|
|
|
HTTPExceptionHandler
|
meth: |
Source code in tempest_fastapi_sdk/api/handlers.py
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | |
make_unhandled_exception_handler
¶
make_unhandled_exception_handler(
*,
log_traceback: bool = True,
include_traceback: bool = False,
log_level: int = ERROR,
) -> UnhandledExceptionHandler
Build the catch-all handler for non-:class:AppException errors.
Default FastAPI/Starlette behavior on uncaught exceptions is to
return a bare Internal Server Error string and emit nothing
beyond the access log line — the actual traceback never reaches
the logger and never reaches the operator. This handler closes
that gap:
- Logs the failure at
log_level(ERROR by default) under thetempest_fastapi_sdk.api.handlerslogger. Whenlog_traceback=True(the default), the full traceback is attached viaexc_infoso the application'sLogUtils/configure_loggingsetup serializes it. The record is flagged with :data:tempest_fastapi_sdk.core.logging.HTTP_500_MARKERsoconfigure_logging(log_dir=...)can route it to a dedicated500.log. - Returns the canonical SDK JSON envelope with
code="INTERNAL_SERVER_ERROR"andstatus_code=500. - When
include_traceback=True(development only) appends the formatted traceback underdetails.tracebackso the failure is visible in the browser too. Leave it off in production — the body would leak module paths, secrets inreproutput and SQL fragments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_traceback
|
bool
|
Whether to attach the full traceback to
the log record via |
True
|
include_traceback
|
bool
|
Whether to surface the traceback in the response body. Off in production. |
False
|
log_level
|
int
|
Logging level used by the catch-all handler. |
ERROR
|
Returns:
| Name | Type | Description |
|---|---|---|
UnhandledExceptionHandler |
UnhandledExceptionHandler
|
An async |
UnhandledExceptionHandler
|
|
|
UnhandledExceptionHandler
|
meth: |
Source code in tempest_fastapi_sdk/api/handlers.py
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
RequestIDMiddleware
¶
Bases: BaseHTTPMiddleware
Bind an X-Request-ID header to the request-scoped context.
Reads the inbound header (or generates a fresh UUID v4 when
absent), stores it via :func:set_request_id so log records
written during the request carry the request_id field, and
echoes the same value back on the response so callers can trace
end-to-end across services.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI application. |
required |
header_name
|
str
|
The header to read/write. Defaults to
|
'X-Request-ID'
|
Source code in tempest_fastapi_sdk/api/middlewares/request_id.py
dispatch
async
¶
Run the wrapped handler with a bound request ID.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request
|
Request
|
The inbound request. |
required |
call_next
|
Callable[[Request], Awaitable[Response]]
|
The downstream ASGI handler. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Response |
Response
|
The handler's response with the request ID |
Response
|
echoed in the configured header. |
Source code in tempest_fastapi_sdk/api/middlewares/request_id.py
IdempotencyMiddleware
¶
IdempotencyMiddleware(
app: ASGIApp,
*,
store: IdempotencyStore,
ttl_seconds: int = 24 * 3600,
header_name: str = IDEMPOTENCY_HEADER,
)
Bases: BaseHTTPMiddleware
ASGI middleware caching responses by Idempotency-Key.
Only mutating verbs (POST / PUT / PATCH /
DELETE) are eligible. The key is scoped per
(method, path, key) so a key reused across different
endpoints doesn't collide.
Add to FastAPI like any other ASGI middleware:
from tempest_fastapi_sdk import (
IdempotencyMiddleware,
MemoryIdempotencyStore,
)
app.add_middleware(
IdempotencyMiddleware,
store=MemoryIdempotencyStore(),
ttl_seconds=24 * 3600,
)
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI app. |
required |
store
|
IdempotencyStore
|
Backend used to cache responses.
Pass :class: |
required |
ttl_seconds
|
int
|
How long to keep cached responses. Stripe defaults to 24 hours — long enough to cover client retries with exponential backoff. |
24 * 3600
|
header_name
|
str
|
Header carrying the idempotency key.
Defaults to the canonical |
IDEMPOTENCY_HEADER
|
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
dispatch
async
¶
Replay cached responses when the same key reappears.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
MemoryIdempotencyStore
¶
In-process :class:IdempotencyStore with TTL eviction.
Single-replica only — a second replica won't see entries stored by the first. Suitable for dev, tests, and small services that haven't scaled out yet.
The eviction is best-effort: TTLs are checked on access; no background thread cleans the dict. Memory grows linearly with cached requests until they expire, so set a sensible TTL.
Initialize the in-memory store.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
get
async
¶
get(key: str) -> CachedResponse | None
Return the cached response, evicting if expired.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
set
async
¶
set(key: str, response: CachedResponse, *, ttl_seconds: int) -> None
Store the response with an expiry.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
RedisIdempotencyStore
¶
:class:IdempotencyStore backed by an async redis client.
The cached payload is encoded as JSON so the schema stays
portable across SDK versions: {"status_code", "headers",
"body_b64", "media_type"} with the body base64-encoded
because Redis values are bytes.
Use this in production / multi-replica deployments. Requires
the [cache] extra so the redis async client is
available.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
_RedisLike
|
Async Redis-like client exposing
|
required |
prefix
|
str
|
Key prefix so idempotency entries don't collide with other cached data. |
'idem:'
|
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
get
async
¶
get(key: str) -> CachedResponse | None
Fetch and decode the cached response.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
set
async
¶
set(key: str, response: CachedResponse, *, ttl_seconds: int) -> None
Serialize and write with EXPIRE.
Source code in tempest_fastapi_sdk/api/middlewares/idempotency.py
BodySizeLimitMiddleware
¶
Pure ASGI middleware enforcing max_bytes per request.
Two checks happen:
- Header check —
Content-Lengthgreater than the cap short-circuits immediately with a413response. This catches the common case where the client knows the size. - Streaming check — for chunked / unknown-length uploads
the middleware tracks bytes seen in the
http.requestmessages and aborts once the cap is crossed.
Excluded paths bypass the check entirely (typical use: an upload endpoint that intentionally accepts larger bodies and enforces its own per-route limit).
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI app. |
required |
max_bytes
|
int
|
Hard cap on the request body in bytes.
|
required |
exclude_paths
|
tuple[str, ...]
|
Path prefixes that
bypass the limit. Match is |
()
|
Source code in tempest_fastapi_sdk/api/middlewares/body_size.py
CSRFMiddleware
¶
CSRFMiddleware(
app: ASGIApp,
*,
cookie_name: str = CSRF_COOKIE_NAME,
header_name: str = CSRF_HEADER_NAME,
exclude_paths: tuple[str, ...] = (),
)
Bases: BaseHTTPMiddleware
Double-submit cookie CSRF guard.
On unsafe methods (POST / PUT / PATCH / DELETE)
the request MUST carry:
- The CSRF cookie (
csrf_tokenby default). - The CSRF header (
X-CSRF-Tokenby default) with the same value.
Missing or mismatched values return 403 with the SDK
envelope. Excluded paths bypass the check — typical use:
/api/ routes that use Authorization: Bearer (not
susceptible to CSRF), or webhook callbacks whose
authentication is signature-based.
Safe methods (GET / HEAD / OPTIONS) always pass.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
Wrapped app. |
required |
cookie_name
|
str
|
Name of the CSRF cookie. |
CSRF_COOKIE_NAME
|
header_name
|
str
|
Name of the CSRF header. |
CSRF_HEADER_NAME
|
exclude_paths
|
tuple[str, ...]
|
Path prefixes that
bypass the check (e.g. |
()
|
Source code in tempest_fastapi_sdk/api/middlewares/csrf.py
dispatch
async
¶
Enforce the double-submit check on unsafe methods.
Source code in tempest_fastapi_sdk/api/middlewares/csrf.py
make_csrf_token_dependency
¶
make_csrf_token_dependency(
*, cookie_name: str = CSRF_COOKIE_NAME
) -> Callable[[Request], str]
Build a FastAPI dependency that issues + returns the CSRF token.
Use this on the route that renders the login page (or the
HTML shell that triggers form submissions). The dependency
sets the cookie on the response when missing, returning the
token so the template can embed it as a hidden input or read
it via document.cookie.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cookie_name
|
str
|
Cookie key — must match
|
CSRF_COOKIE_NAME
|
Returns:
| Type | Description |
|---|---|
Callable[[Request], str]
|
Callable[[Request], str]: FastAPI dependency. |
Source code in tempest_fastapi_sdk/api/middlewares/csrf.py
generate_csrf_token
¶
Mint a fresh CSRF token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n_bytes
|
int
|
Entropy bytes. 32 yields a 43-char URL-safe string; bring this above 16 to stay above the birthday-bound on the cookie set. |
32
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
URL-safe base64 token without padding. |
Source code in tempest_fastapi_sdk/api/middlewares/csrf.py
LocalUploadStorage
¶
Disk-backed :class:UploadStorage using aiofiles.
Writes chunks under base_dir and refuses keys that resolve
outside the base — same path-traversal protection
:class:UploadUtils already applied. The base_dir is
created (with parents) on instantiation.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_dir
|
Path | str
|
Root directory for all writes. |
required |
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/storage_backends.py
write_stream
async
¶
write_stream(
key: str,
chunks: AsyncIterator[bytes],
*,
content_type: str = "application/octet-stream",
metadata: dict[str, str] | None = None,
max_size_bytes: int | None = None,
validator: ContentValidator | None = None,
) -> UploadResult
Persist chunks to base_dir / key.
content_type and metadata are accepted for protocol
parity but ignored — the local filesystem has nowhere to
store them.
Source code in tempest_fastapi_sdk/utils/storage_backends.py
delete
async
¶
Delete base_dir / key when present.
exists
async
¶
presigned_url
async
¶
Local storage cannot mint URLs — always None.
MinIOUploadStorage
¶
MinIOUploadStorage(client: AsyncMinIOClient, *, bucket: str | None = None)
:class:UploadStorage backed by :class:AsyncMinIOClient.
Reuses an existing client instance — typically the one wired
on the FastAPI app — so the connection pool is shared. The
bucket falls back to the client's default_bucket.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
AsyncMinIOClient
|
A configured MinIO client. |
required |
bucket
|
str | None
|
Target bucket. |
None
|
Source code in tempest_fastapi_sdk/utils/storage_backends.py
write_stream
async
¶
write_stream(
key: str,
chunks: AsyncIterator[bytes],
*,
content_type: str = "application/octet-stream",
metadata: dict[str, str] | None = None,
max_size_bytes: int | None = None,
validator: ContentValidator | None = None,
) -> UploadResult
Buffer chunks, validate, then put_object.
S3 needs the content length upfront, so the stream is
materialized in memory before the upload starts. For
very large objects, prefer presigned_put_url and let
the client upload directly.
Source code in tempest_fastapi_sdk/utils/storage_backends.py
delete
async
¶
Delete the object — always returns True (S3 idempotent).
exists
async
¶
presigned_url
async
¶
Return a presigned GET URL.
Source code in tempest_fastapi_sdk/utils/storage_backends.py
HTTPClient
¶
HTTPClient(
*,
base_url: str = "",
timeout: float = 10.0,
retry_policy: RetryPolicy | None = None,
failure_threshold: int = 5,
recovery_seconds: float = 30.0,
default_headers: Mapping[str, str] | None = None,
verify_tls: bool = True,
propagate_request_id: bool = True,
)
Async HTTP client with retries, circuit-breaker and request-id propagation.
Example:
>>> client = HTTPClient(base_url="https://api.example.com")
>>> async with client:
... response = await client.get("/users/me")
... payload: dict[str, Any] = response.json()
The client is safe to share across requests on the same
event loop — internally each call uses the shared
:class:httpx.AsyncClient connection pool.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base_url
|
str
|
Prepended to relative paths. Use empty string to require absolute URLs at the call site. |
''
|
timeout
|
float
|
Per-request timeout in seconds. Overridable per call. |
10.0
|
retry_policy
|
RetryPolicy | None
|
Retry configuration.
|
None
|
failure_threshold
|
int
|
Consecutive 5xx/network errors
that trip the circuit per host. |
5
|
recovery_seconds
|
float
|
Seconds the breaker stays open before allowing one half-open probe. |
30.0
|
default_headers
|
Mapping[str, str] | None
|
Headers
attached to every request (e.g. |
None
|
verify_tls
|
bool
|
Whether to verify TLS certificates.
Default |
True
|
propagate_request_id
|
bool
|
When |
True
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/http_client.py
aclose
async
¶
request
async
¶
request(
method: str,
url: str,
*,
params: Mapping[str, Any] | None = None,
json: Any = None,
data: Any = None,
headers: Mapping[str, str] | None = None,
timeout: float | None = None,
) -> Response
Perform an HTTP request with retries and circuit-breaker.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method
|
str
|
HTTP verb ( |
required |
url
|
str
|
Absolute URL or path relative to |
required |
params
|
Mapping[str, Any] | None
|
Query-string params. |
None
|
json
|
Any
|
JSON-serializable body. Mutually exclusive
with |
None
|
data
|
Any
|
Form body. Mutually exclusive with |
None
|
headers
|
Mapping[str, str] | None
|
Per-request headers
merged on top of |
None
|
timeout
|
float | None
|
Override for this call. |
None
|
Returns:
| Type | Description |
|---|---|
Response
|
httpx.Response: The successful response. Caller checks |
Response
|
|
Response
|
not retried). |
Raises:
| Type | Description |
|---|---|
CircuitOpenError
|
When the per-host breaker is open. |
HTTPError
|
When all attempts failed (last exception is re-raised). |
Source code in tempest_fastapi_sdk/utils/http_client.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | |
get
async
¶
post
async
¶
put
async
¶
patch
async
¶
delete
async
¶
RetryPolicy
dataclass
¶
RetryPolicy(
max_attempts: int = 3,
backoff_initial_seconds: float = 0.5,
backoff_max_seconds: float = 8.0,
retry_statuses: frozenset[int] = (lambda: frozenset({429, 500, 502, 503, 504}))(),
)
Bounded exponential backoff for retried requests.
The first retry sleeps for backoff_initial_seconds; each
subsequent retry doubles the wait, capped at
backoff_max_seconds. Total retries are bounded by
max_attempts (the first try counts).
Attributes:
| Name | Type | Description |
|---|---|---|
max_attempts |
int
|
Total tries including the first.
|
backoff_initial_seconds |
float
|
Sleep before the second attempt. |
backoff_max_seconds |
float
|
Hard cap per sleep. |
retry_statuses |
frozenset[int]
|
HTTP status codes worth
retrying. Defaults to common 5xx; |
sleep_for
¶
Compute the sleep between attempt n and attempt n+1.
CircuitOpenError
¶
Bases: Exception
Raised when the circuit-breaker rejects a call.
Carries the host that tripped the breaker so callers can branch on it (e.g. fall back to a cache or a queue).
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
The host whose breaker is open. |
required |
Source code in tempest_fastapi_sdk/utils/http_client.py
GoogleOAuthClient
¶
GoogleOAuthClient(
*,
client_id: str,
client_secret: str,
redirect_uri: str,
scopes: list[str] | None = None,
http_client: HTTPClient | None = None,
)
Bases: _BaseOAuthClient
Google identity client (OIDC-compatible).
Default scopes: openid email profile.
Source code in tempest_fastapi_sdk/api/oauth.py
GitHubOAuthClient
¶
GitHubOAuthClient(
*,
client_id: str,
client_secret: str,
redirect_uri: str,
scopes: list[str] | None = None,
http_client: HTTPClient | None = None,
)
Bases: _BaseOAuthClient
GitHub OAuth client.
GitHub doesn't issue an id_token — the user identity comes
from GET /user. Default scopes: read:user user:email.
Source code in tempest_fastapi_sdk/api/oauth.py
OIDCProvider
¶
OIDCProvider(
*,
client_id: str,
client_secret: str,
redirect_uri: str,
authorize_url: str,
token_url: str,
userinfo_url: str | None = None,
provider_name: str = "oidc",
scopes: list[str] | None = None,
http_client: HTTPClient | None = None,
)
Bases: _BaseOAuthClient
Generic OIDC provider — works with any conformant IdP.
Pass the authorize / token / userinfo endpoints explicitly,
or fetch them once at boot from the IdP's discovery document
at ${issuer}/.well-known/openid-configuration and pass the
URLs in. Default scopes: openid email profile.
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client_id
|
str
|
App client id at the IdP. |
required |
client_secret
|
str
|
App client secret. |
required |
redirect_uri
|
str
|
Registered callback URL. |
required |
authorize_url
|
str
|
IdP's authorize endpoint. |
required |
token_url
|
str
|
IdP's token endpoint. |
required |
userinfo_url
|
str | None
|
IdP's userinfo endpoint.
|
None
|
provider_name
|
str
|
Key embedded in
:attr: |
'oidc'
|
scopes
|
list[str] | None
|
Scopes to request. |
None
|
http_client
|
HTTPClient | None
|
Shared client. |
None
|
Source code in tempest_fastapi_sdk/api/oauth.py
OAuthUser
dataclass
¶
OAuthUser(
provider: str,
subject: str,
email: str | None = None,
name: str | None = None,
picture: str | None = None,
raw: dict[str, Any] = dict(),
)
Normalized user identity returned by every provider.
Different IdPs use different field names (sub vs id,
picture vs avatar_url, name vs login). This
dataclass is the single shape the rest of the application sees.
Attributes:
| Name | Type | Description |
|---|---|---|
provider |
str
|
Provider key ( |
subject |
str
|
Stable per-provider user id. Combine with
|
email |
str | None
|
Verified email when the provider returned one. Some IdPs gate this behind extra scopes. |
name |
str | None
|
Human-readable display name. |
picture |
str | None
|
Avatar / profile picture URL. |
raw |
dict[str, Any]
|
Full provider payload for advanced cases (custom claims, role mappings). |
OAuthTokens
dataclass
¶
OAuthTokens(
access_token: str,
token_type: str,
refresh_token: str | None = None,
id_token: str | None = None,
expires_in: int | None = None,
scope: str | None = None,
raw: dict[str, Any] = dict(),
)
Tokens returned by the IdP after the authorization-code exchange.
Attributes:
| Name | Type | Description |
|---|---|---|
access_token |
str
|
Bearer token to call provider APIs. |
token_type |
str
|
Usually |
refresh_token |
str | None
|
Refresh token when offline access was requested. |
id_token |
str | None
|
OIDC id token (JWT). Present on OIDC flows, absent on plain OAuth2. |
expires_in |
int | None
|
Lifetime of |
scope |
str | None
|
Space-separated scopes granted. |
raw |
dict[str, Any]
|
Full token-endpoint response. |
apply_cors
¶
apply_cors(
app: FastAPI,
settings: CORSSettings | None = None,
*,
origins: list[str] | None = None,
allow_credentials: bool | None = None,
allow_methods: list[str] | None = None,
allow_headers: list[str] | None = None,
expose_headers: list[str] | None = None,
max_age: int | None = None,
) -> None
Attach CORSMiddleware to app using SDK conventions.
All overrides are optional; when provided they take precedence
over the matching field on settings. When neither is provided,
a permissive default suitable for local development is used
(origins=["*"], no credentials).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
FastAPI
|
The application to mutate. |
required |
settings
|
CORSSettings | None
|
Source of the defaults. When
|
None
|
origins
|
list[str] | None
|
Override for
:attr: |
None
|
allow_credentials
|
bool | None
|
Override. |
None
|
allow_methods
|
list[str] | None
|
Override. |
None
|
allow_headers
|
list[str] | None
|
Override. |
None
|
expose_headers
|
list[str] | None
|
Override. |
None
|
max_age
|
int | None
|
Override. |
None
|
Source code in tempest_fastapi_sdk/api/middlewares/cors.py
make_health_router
¶
make_health_router(
*,
db: AsyncDatabaseManager | None = None,
checks: dict[str, HealthCheck] | None = None,
prefix: str = "/health",
tag: str = "health",
version: str | None = None,
expose_checks: bool = True,
) -> APIRouter
Build the canonical /health router.
Two endpoints are mounted:
GET <prefix>/liveness— always returns{"status": "ok"}so orchestrators can confirm the process is up. Should not depend on any external resource (Kubernetes treats failed liveness probes as "restart the pod"). This endpoint takes precedence over readiness for that reason.GET <prefix>/readiness— runs every configured check and returns200only when all pass. Returns503when at least one fails.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
db
|
AsyncDatabaseManager | None
|
When provided, a
|
None
|
checks
|
dict[str, HealthCheck] | None
|
Extra readiness
checks keyed by name (e.g. |
None
|
prefix
|
str
|
The URL prefix for the router. Defaults to
|
'/health'
|
tag
|
str
|
OpenAPI tag applied to both endpoints. |
'health'
|
version
|
str | None
|
When provided, attached to the
readiness payload as |
None
|
expose_checks
|
bool
|
Whether to surface the per-dependency
breakdown in the readiness payload. Defaults to |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
A router ready to |
APIRouter
|
FastAPI app. |
Source code in tempest_fastapi_sdk/api/routers/health.py
make_logs_router
¶
make_logs_router(
*,
log_dir: str | Path = "logs",
token_secret: str = "",
prefix: str = "/logs",
tag: str = "logs",
header_name: str = "X-Token",
default_page_size: int = 20,
max_page_size: int = 200,
) -> APIRouter
Build a router that serves the on-disk JSON logs, paginated.
Mounts GET <prefix> which reads the files produced by
:func:tempest_fastapi_sdk.configure_logging (called with
log_dir=...), filters them, and returns a
:class:BasePaginationSchema of :class:LogEntrySchema. Newest
records come first.
The endpoint is gated by a shared-secret X-Token header via
:func:make_token_dependency. An empty token_secret disables
the check (development only) — never ship log access unauthenticated
in production, the payload exposes tracebacks and request metadata.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
log_dir
|
str | Path
|
Directory holding the log files. Must
match the |
'logs'
|
token_secret
|
str
|
Shared secret for the |
''
|
prefix
|
str
|
URL prefix for the router. Defaults to
|
'/logs'
|
tag
|
str
|
OpenAPI tag applied to the endpoint. |
'logs'
|
header_name
|
str
|
Auth header name. Defaults to |
'X-Token'
|
default_page_size
|
int
|
Page size when the caller omits it. |
20
|
max_page_size
|
int
|
Upper bound enforced on |
200
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
A router ready to |
Source code in tempest_fastapi_sdk/api/routers/logs.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | |
PrometheusMiddleware
¶
PrometheusMiddleware(
app: ASGIApp,
*,
registry: CollectorRegistry,
latency_buckets: tuple[float, ...] = DEFAULT_LATENCY_BUCKETS,
)
Bases: BaseHTTPMiddleware
ASGI middleware tracking HTTP requests on three core metrics.
Registered series:
http_requests_total{method, path, status}(Counter) — every request counts here once the response status is known.http_request_duration_seconds{method, path}(Histogram) — end-to-end latency in seconds.http_requests_in_progress{method}(Gauge) — live inflight count, decremented in afinallyso dropped connections never leave stale gauges.
The path label uses the route template (e.g.
/orders/{order_id}) when the request hit a FastAPI route,
not the raw URL — that keeps the cardinality bounded.
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
The wrapped ASGI app. |
required |
registry
|
CollectorRegistry
|
Shared registry. Reuse the
same instance for |
required |
latency_buckets
|
tuple[float, ...]
|
Histogram bucket upper bounds in seconds. |
DEFAULT_LATENCY_BUCKETS
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/api/routers/metrics.py
dispatch
async
¶
Wrap the request with counters, gauge, and latency timer.
Source code in tempest_fastapi_sdk/api/routers/metrics.py
make_prometheus_router
¶
make_prometheus_router(
*,
registry: CollectorRegistry,
path: str = "/metrics",
dependencies: list[Callable[..., Any]] | None = None,
) -> APIRouter
Build the GET /metrics router scraping registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
registry
|
CollectorRegistry
|
The same registry passed to
:class: |
required |
path
|
str
|
Endpoint path. Defaults to |
'/metrics'
|
dependencies
|
list | None
|
FastAPI dependencies to attach
— typically |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Mount with |
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/api/routers/metrics.py
make_prometheus_registry
¶
Build a fresh :class:CollectorRegistry.
Use this once at app boot, share the registry across the
middleware and any custom metric you register, then pass it to
:func:make_prometheus_router.
Returns:
| Name | Type | Description |
|---|---|---|
CollectorRegistry |
CollectorRegistry
|
An empty registry, decoupled from the |
CollectorRegistry
|
|
|
CollectorRegistry
|
metrics between runs. |
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/api/routers/metrics.py
tempest_fastapi_sdk.auth¶
UserAuthService
¶
UserAuthService(
*,
user_model: type[BaseUserModel],
token_model: type[BaseUserTokenModel],
auth_settings: AuthSettings,
jwt_settings: JWTSettings,
email: EmailUtils | None = None,
passwords: PasswordUtils | None = None,
jwt: JWTUtils | None = None,
db: AsyncDatabaseManager | None = None,
)
Compose UserModel + UserTokenModel into a full auth flow.
Example:
>>> service = UserAuthService(
... db=db,
... user_model=UserModel,
... token_model=UserTokenModel,
... auth_settings=settings,
... jwt_settings=settings,
... email=email_utils,
... )
>>> async with db.get_session_context() as s:
... result = await service.signup(s, payload)
Every method takes the active AsyncSession explicitly so
callers control the transaction boundary — the service never
opens its own session.
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
Concrete user model
— usually |
required |
token_model
|
type[BaseUserTokenModel]
|
Concrete token
model — usually |
required |
auth_settings
|
AuthSettings
|
The mixin populating activation / reset behavior. |
required |
jwt_settings
|
JWTSettings
|
The mixin populating signing keys and TTLs. |
required |
email
|
EmailUtils | None
|
Configured email helper.
When |
None
|
passwords
|
PasswordUtils | None
|
Override for tests; defaults to a fresh instance. |
None
|
jwt
|
JWTUtils | None
|
Override for tests; defaults
to one built from |
None
|
db
|
AsyncDatabaseManager | None
|
Optional handle for services that open their own sessions inside helpers like background tasks. |
None
|
Source code in tempest_fastapi_sdk/auth/service.py
signup
async
¶
signup(
session: AsyncSession, *, email: str, password: str, name: str | None = None
) -> tuple[BaseUserModel, ActivationToken | None]
Create a user row and (optionally) issue an activation token.
When AUTH_AUTO_ACTIVATE is true, the user is marked
is_active=True immediately and None is returned in
the second tuple slot — the caller can mint JWTs right
away. Otherwise the user is inserted with is_active=False
and an activation token is returned for the caller to mail
or echo back.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
email
|
str
|
Account email — normalized to lowercase. |
required |
password
|
str
|
Plaintext password; length is enforced
against |
required |
name
|
str | None
|
Optional display name; passed through to the model when the column exists. |
None
|
Returns:
| Type | Description |
|---|---|
BaseUserModel
|
tuple[BaseUserModel, ActivationToken | None]: The |
ActivationToken | None
|
persisted user and (when activation is required) the |
tuple[BaseUserModel, ActivationToken | None]
|
token to surface. |
Raises:
| Type | Description |
|---|---|
ValidationException
|
When the password is too short. |
ConflictException
|
When the email is already taken. |
Source code in tempest_fastapi_sdk/auth/service.py
activate
async
¶
activate(session: AsyncSession, *, token: str) -> BaseUserModel
Consume an activation token and flip is_active on the user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
token
|
str
|
Plaintext token from the activation URL. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
BaseUserModel |
BaseUserModel
|
The freshly-activated user. |
Raises:
| Type | Description |
|---|---|
InvalidTokenException
|
When the token is malformed, expired, already used, or doesn't match a row. |
Source code in tempest_fastapi_sdk/auth/service.py
login
async
¶
login(session: AsyncSession, *, email: str, password: str) -> BaseUserModel
Validate credentials and return the matching user row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
email
|
str
|
Login identifier. |
required |
password
|
str
|
Plaintext password. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
BaseUserModel |
BaseUserModel
|
The authenticated user. |
Raises:
| Type | Description |
|---|---|
UnauthorizedException
|
On any failure — wrong password, missing user, inactive user. The message is deliberately generic so attackers can't enumerate accounts. |
Source code in tempest_fastapi_sdk/auth/service.py
request_password_reset
async
¶
request_password_reset(
session: AsyncSession, *, email: str
) -> PasswordResetToken | None
Mint a one-shot reset token for email.
Returns None when no user matches — callers should
still respond 202 to avoid leaking account
existence. Sends the email (when EmailUtils is wired)
or returns the token for inline display per the
AUTH_RETURN_TOKEN_IN_RESPONSE flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
email
|
str
|
Account email. |
required |
Returns:
| Type | Description |
|---|---|
PasswordResetToken | None
|
PasswordResetToken | None: The token bundle when the |
PasswordResetToken | None
|
caller is configured to surface the link, |
PasswordResetToken | None
|
when the link is meant to live only in the email. |
Source code in tempest_fastapi_sdk/auth/service.py
confirm_password_reset
async
¶
confirm_password_reset(
session: AsyncSession, *, token: str, new_password: str
) -> BaseUserModel
Consume a reset token and replace the user's password.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
token
|
str
|
Plaintext token from the reset URL. |
required |
new_password
|
str
|
Plaintext replacement password. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
BaseUserModel |
BaseUserModel
|
The user whose password was rotated. |
Raises:
| Type | Description |
|---|---|
ValidationException
|
When the new password is too short. |
InvalidTokenException
|
On bad / expired / spent tokens. |
Source code in tempest_fastapi_sdk/auth/service.py
issue_jwt_pair
¶
issue_jwt_pair(user: BaseUserModel) -> tuple[str, str]
Return (access, refresh) JWTs for an authenticated user.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user
|
BaseUserModel
|
The authenticated user. |
required |
Returns:
| Type | Description |
|---|---|
tuple[str, str]
|
tuple[str, str]: |
Source code in tempest_fastapi_sdk/auth/service.py
peek_token
async
¶
peek_token(
session: AsyncSession, *, token: str, purpose: UserTokenPurpose
) -> tuple[BaseUserTokenModel, BaseUserModel]
Validate a token + load its user without consuming it.
Mirrors :meth:_consume_token (raises on
invalid/expired/already-used tokens) but leaves
used_at untouched — used by GET endpoints in
backend-only mode that need to render a page (e.g. the
password-reset form) before the user actually submits.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
token
|
str
|
Plaintext token. |
required |
purpose
|
UserTokenPurpose
|
Expected token purpose. |
required |
Returns:
| Type | Description |
|---|---|
BaseUserTokenModel
|
tuple[BaseUserTokenModel, BaseUserModel]: The token |
BaseUserModel
|
record and its associated user. |
Raises:
| Type | Description |
|---|---|
InvalidTokenException
|
On unknown / already-used / expired tokens. |
NotFoundException
|
When the token references a user that no longer exists. |
Source code in tempest_fastapi_sdk/auth/service.py
make_auth_router
¶
make_auth_router(
service: UserAuthService,
*,
session_factory: Callable[[], AsyncIterator[AsyncSession]],
prefix: str = "/auth",
tags: list[str] | None = None,
template_dir: str | None = None,
) -> APIRouter
Build the bundled auth router.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
UserAuthService
|
The configured service handling signup / activation / reset. |
required |
session_factory
|
Callable[[], AsyncIterator[AsyncSession]]
|
FastAPI dependency yielding an async session. Typically
wired as |
required |
prefix
|
str
|
URL prefix; defaults to |
'/auth'
|
tags
|
list[str] | None
|
OpenAPI tags. Defaults to
|
None
|
template_dir
|
str | None
|
Optional directory holding
HTML templates that override the SDK-bundled
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Ready to mount with |
Source code in tempest_fastapi_sdk/auth/router.py
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | |
SignupSchema
¶
Bases: BaseSchema
Request body for POST /auth/signup.
Carries the credentials and the optional display name a new
account starts with. The email is normalized to lowercase
before insert (matches the unique-index convention every SDK
user table follows); the password is hashed with bcrypt by
:class:tempest_fastapi_sdk.PasswordUtils and never stored
in plaintext.
Attributes:
| Name | Type | Description |
|---|---|---|
email |
EmailStr
|
Login identifier — validated by
|
password |
str
|
Plaintext password. Length floor is
enforced both here (schema-level) and inside
:class: |
name |
str | None
|
Optional display name shown in the
admin UI / front-end profile. |
SignupResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/signup.
The shape depends on the active settings:
- When
AUTH_AUTO_ACTIVATE=Truethe user is born active,activation_required=Falseand bothaccess_token/refresh_tokenare populated — the client can log in immediately. - When
AUTH_AUTO_ACTIVATE=False(production default) the user must confirm the activation link before logging in.activation_required=True, the tokens stayNoneandactivation_urlis set only whenAUTH_RETURN_TOKEN_IN_RESPONSE=True(dev) or when the[email]extra isn't wired (so the link has to ship via the response instead of via SMTP).
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
Primary key of the freshly-inserted row. |
activation_required |
bool
|
Whether the user still needs to confirm via the activation link. |
activation_url |
str | None
|
Front-end URL the user must
visit. |
access_token |
str | None
|
Short-lived JWT. Only set
when |
refresh_token |
str | None
|
Long-lived JWT. Only set
when |
LoginSchema
¶
Bases: BaseSchema
Request body for POST /auth/login.
Standard email + password authentication. Both error paths
(wrong password / unknown email / inactive user) collapse
into the same generic UnauthorizedException so attackers
can't enumerate accounts by reading the response.
Attributes:
| Name | Type | Description |
|---|---|---|
email |
EmailStr
|
Login identifier. |
password |
str
|
Plaintext password — verified against the bcrypt hash stored on the row. |
LoginResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/login and the password-reset confirm.
Issued only when credentials validate; the bundled router
reuses this shape for both POST /auth/login and
POST /auth/password-reset/confirm since both flows end
with an authenticated session.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the authenticated user. |
access_token |
str
|
Short-lived JWT. |
refresh_token |
str
|
Long-lived JWT. |
ActivationResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/activate/{token}.
Returned after the SDK has consumed a one-shot activation
token and flipped the user's is_active=True. The user is
automatically logged in — both JWTs are issued so the
front-end can complete the post-confirmation redirect in one
round-trip.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the freshly-activated user. |
access_token |
str
|
Short-lived JWT. |
refresh_token |
str
|
Long-lived JWT. |
PasswordResetRequestSchema
¶
Bases: BaseSchema
Request body for POST /auth/password-reset/request.
The endpoint always returns 202 with a generic message —
even when the email isn't on file — so probing the endpoint
can't enumerate accounts. The reset link travels via email
(production) or in the response body when
AUTH_RETURN_TOKEN_IN_RESPONSE=True (dev).
Attributes:
| Name | Type | Description |
|---|---|---|
email |
EmailStr
|
Email of the account asking for a reset. |
PasswordResetResponseSchema
¶
Bases: BaseSchema
Response body for POST /auth/password-reset/request.
message is the same generic string regardless of whether
the email matched an account. reset_url is populated only
when AUTH_RETURN_TOKEN_IN_RESPONSE=True or when the
[email] extra isn't installed — otherwise the link only
travels through SMTP.
Attributes:
| Name | Type | Description |
|---|---|---|
message |
str
|
Human-readable summary of the next step. Always identical across the "email found" / "email not found" branches. |
reset_url |
str | None
|
Front-end reset URL when the
caller asked for an inline response, |
PasswordResetConfirmSchema
¶
Bases: BaseSchema
Request body for POST /auth/password-reset/confirm.
Carries the opaque token the user copied from the reset
link plus the replacement password. The service consumes the
token (one-shot — used_at is stamped) and replaces the
bcrypt hash atomically.
Attributes:
| Name | Type | Description |
|---|---|---|
token |
str
|
Opaque token issued by |
new_password |
str
|
Plaintext replacement password. Length floor is enforced both schema-side and inside the service. |
ActivationToken
¶
Bases: BaseSchema
Service-level result of issuing an account-activation token.
Returned by :meth:UserAuthService.signup when activation is
required — i.e. when AUTH_AUTO_ACTIVATE is false. The
plaintext token is included here exactly once; only its
SHA-256 hash is persisted, so this value cannot be recovered
later. Use it to mail the activation link, log it during
tests, or hand it back to the client in dev mode.
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the user the token authorizes. |
token |
str
|
Plaintext token — show once, never store. |
url |
str
|
Front-end activation URL with the token
already substituted into |
expires_at |
datetime
|
UTC timestamp the token becomes invalid (default 7 days after issuance). |
PasswordResetToken
¶
Bases: BaseSchema
Service-level result of issuing a password-reset token.
Returned by :meth:UserAuthService.request_password_reset
when the email matches a user and the caller asked the
service to surface the link (either via
AUTH_RETURN_TOKEN_IN_RESPONSE=True or because no
:class:EmailUtils was wired). The plaintext token is
one-shot, hashed at rest, and expires after
AUTH_PASSWORD_RESET_TTL_SECONDS (default 1 hour).
Attributes:
| Name | Type | Description |
|---|---|---|
user_id |
UUID
|
UUID of the user whose password the token authorizes resetting. |
token |
str
|
Plaintext token — display once, never store. |
url |
str
|
Front-end reset URL with the token already
substituted into |
expires_at |
datetime
|
UTC timestamp the token becomes invalid. |
tempest_fastapi_sdk.sessions¶
SessionAuth
¶
SessionAuth(
*,
user_model: type[BaseUserModel],
store: SessionStore,
settings: SessionSettings,
passwords: PasswordUtils | None = None,
)
Server-side session lifecycle orchestrator.
Mount one instance per FastAPI app. Stateless — the only
state lives in the injected :class:SessionStore and the
project's UserModel table.
Initialize the service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
Concrete user model
(typically |
required |
store
|
SessionStore
|
Persistence backend
(:class: |
required |
settings
|
SessionSettings
|
TTL / cookie / rotation flags driving the lifecycle. |
required |
passwords
|
PasswordUtils | None
|
Override for tests;
defaults to a fresh |
None
|
Source code in tempest_fastapi_sdk/sessions/service.py
authenticate
async
¶
authenticate(session: AsyncSession, *, email: str, password: str) -> BaseUserModel
Validate credentials and return the matching user row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
Active SQLAlchemy session. |
required |
email
|
str
|
Account email. |
required |
password
|
str
|
Plaintext password. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
BaseUserModel |
BaseUserModel
|
The authenticated user. |
Raises:
| Type | Description |
|---|---|
UnauthorizedException
|
On any failure — wrong password, missing user, inactive user. The message is deliberately generic so attackers cannot enumerate accounts via timing or wording. |
Source code in tempest_fastapi_sdk/sessions/service.py
login
async
¶
login(
*,
user_id: UUID,
ip: str | None = None,
user_agent: str | None = None,
previous_session_id: str | None = None,
) -> tuple[Session, str]
Mint a brand-new session for user_id.
When :attr:SessionSettings.SESSION_ROTATE_ON_LOGIN is
True (default) and previous_session_id is provided,
the previous session is evicted before the new one is
issued — closes the session-fixation attack window.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
The user the session belongs to. |
required |
ip
|
str | None
|
Client IP (resolve via
:func: |
None
|
user_agent
|
str | None
|
Raw User-Agent header. |
None
|
previous_session_id
|
str | None
|
Plaintext cookie value
of the session being replaced. Pass it when a
request already carries a session cookie — typical
during step-up login. Ignored when
|
None
|
Returns:
| Type | Description |
|---|---|
Session
|
tuple[Session, str]: The persisted session row and the |
str
|
plaintext id to ship via |
tuple[Session, str]
|
is not persisted — losing it means logging the user |
tuple[Session, str]
|
out. |
Source code in tempest_fastapi_sdk/sessions/service.py
resolve
async
¶
resolve(session_id_plaintext: str) -> Session | None
Look up + (optionally) refresh a session by its cookie value.
Returns None when the cookie does not match a live
session — middleware / dependencies treat that as
"unauthenticated".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id_plaintext
|
str
|
Raw cookie value. |
required |
Returns:
| Type | Description |
|---|---|
Session | None
|
Session | None: The resolved session, or |
Source code in tempest_fastapi_sdk/sessions/service.py
revoke
async
¶
Invalidate one session by its plaintext id. Idempotent.
revoke_all
async
¶
list_sessions
async
¶
list_sessions(
user_id: UUID, *, current_session_id_plaintext: str | None = None
) -> list[SessionSummarySchema]
Return public-safe summaries of user_id's sessions.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
Owner whose sessions to list. |
required |
current_session_id_plaintext
|
str | None
|
Optional
plaintext cookie of the session resolving the
current request — used to set |
None
|
Returns:
| Type | Description |
|---|---|
list[SessionSummarySchema]
|
list[SessionSummarySchema]: One entry per live |
list[SessionSummarySchema]
|
session, oldest first. |
Source code in tempest_fastapi_sdk/sessions/service.py
revoke_by_public_id
async
¶
Revoke one session belonging to user_id via its public id.
The public id is the 32-char prefix of the hashed session
id (see :class:SessionSummarySchema). The match scans the
user's live sessions — fast in practice because users
rarely have more than a handful.
Raises:
| Type | Description |
|---|---|
NotFoundException
|
When no live session of |
Source code in tempest_fastapi_sdk/sessions/service.py
make_session_router
¶
make_session_router(
service: SessionAuth,
*,
session_factory: Callable[[], AsyncIterator[AsyncSession]],
prefix: str = "/auth/session",
tags: list[str] | None = None,
) -> APIRouter
Build the bundled session router.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
service
|
SessionAuth
|
Configured auth service that talks to the store + verifies passwords against the user model. |
required |
session_factory
|
Callable[[], AsyncIterator[AsyncSession]]
|
FastAPI dependency yielding an async SQLAlchemy session
— typically |
required |
prefix
|
str
|
URL prefix. Defaults to |
'/auth/session'
|
tags
|
list[str] | None
|
OpenAPI tags. Defaults to
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Ready to mount with |
Source code in tempest_fastapi_sdk/sessions/router.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
SessionMiddleware
¶
SessionMiddleware(
app: ASGIApp, *, session_auth: SessionAuth, settings: SessionSettings
)
Bases: BaseHTTPMiddleware
ASGI middleware that resolves the session cookie per request.
Attach with::
app.add_middleware(
SessionMiddleware,
session_auth=session_auth,
settings=session_settings,
)
After the middleware runs, every handler in the chain can read
request.state.session — a :class:Session instance when
the cookie was valid, None otherwise. Handlers that require
authentication should depend on
:func:make_session_dependency instead of poking
request.state directly so missing sessions raise a clean
401 envelope.
Initialize the middleware.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
ASGIApp
|
Wrapped ASGI app — Starlette passes this
automatically when used with |
required |
session_auth
|
SessionAuth
|
Configured service used to resolve cookies into sessions. |
required |
settings
|
SessionSettings
|
Read for the cookie name. |
required |
Source code in tempest_fastapi_sdk/sessions/middleware.py
dispatch
async
¶
Resolve the cookie before delegating to the route handler.
Source code in tempest_fastapi_sdk/sessions/middleware.py
make_session_dependency
¶
make_session_dependency(
*, required: bool = True
) -> Callable[[Request], Session | None]
Build a FastAPI dependency that returns the resolved session.
The dependency reads request.state.session populated by
:class:SessionMiddleware. Mount the middleware on the app
BEFORE you use the dependency or it always returns None
/raises UnauthorizedException.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
required
|
bool
|
When |
True
|
Returns:
| Type | Description |
|---|---|
Callable[[Request], Session | None]
|
A FastAPI dependency callable. |
Source code in tempest_fastapi_sdk/sessions/dependencies.py
SessionStore
¶
MemorySessionStore
¶
In-process :class:SessionStore for dev, tests, single-replica.
Stores sessions in a dict keyed by their hashed id; a secondary
index by user_id powers list_by_user /
delete_by_user without scanning. Expired rows are pruned on
access — no background task needed.
Initialize the in-memory store.
Source code in tempest_fastapi_sdk/sessions/store.py
get
async
¶
get(session_id_hash: str) -> Session | None
Return the live session for session_id_hash or None.
Source code in tempest_fastapi_sdk/sessions/store.py
set
async
¶
set(session: Session) -> None
Persist (or overwrite) session.
Source code in tempest_fastapi_sdk/sessions/store.py
delete
async
¶
Remove a single session. Idempotent.
Source code in tempest_fastapi_sdk/sessions/store.py
delete_by_user
async
¶
Remove every session for user_id. Returns count deleted.
Source code in tempest_fastapi_sdk/sessions/store.py
list_by_user
async
¶
list_by_user(user_id: UUID) -> list[Session]
Return every live session for user_id (oldest first).
Source code in tempest_fastapi_sdk/sessions/store.py
RedisSessionStore
¶
:class:SessionStore backed by an async redis client.
Schema:
- Each session is stored at
{prefix}sess:{hash}as JSON with a TTL set to(expires_at - now)so Redis evicts the key on its own — no janitor process needed. - The user → session index lives at
{prefix}user:{user_id}as a Redis SET of session hashes. Entries are removed ondeleteand the whole SET is dropped ondelete_by_user.
Requires the [cache] extra so the redis async client is
available.
Initialize the Redis-backed store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
Redis
|
Async Redis client (e.g.
|
required |
prefix
|
str
|
Key prefix so session keys do not collide with other cached data. |
'tempest:'
|
Source code in tempest_fastapi_sdk/sessions/store.py
get
async
¶
get(session_id_hash: str) -> Session | None
Return the live session for session_id_hash or None.
Source code in tempest_fastapi_sdk/sessions/store.py
set
async
¶
set(session: Session) -> None
Persist (or overwrite) session with a TTL matching expires_at.
Source code in tempest_fastapi_sdk/sessions/store.py
delete
async
¶
Remove a single session. Idempotent.
Source code in tempest_fastapi_sdk/sessions/store.py
delete_by_user
async
¶
Remove every session for user_id. Returns count deleted.
Source code in tempest_fastapi_sdk/sessions/store.py
list_by_user
async
¶
list_by_user(user_id: UUID) -> list[Session]
Return every live session for user_id (oldest first).
Source code in tempest_fastapi_sdk/sessions/store.py
Session
¶
Bases: BaseSchema
A live server-side session.
Stored in the configured :class:SessionStore keyed by the
SHA-256 hash of the session id (the plaintext lives only in the
cookie). Mirrors what every session-backed auth flow needs:
user identity, lifetime bounds, originating client metadata for
revocation UX ("you're signed in on Chrome from São Paulo"),
and a free-form data bag for app-level state.
Attributes:
| Name | Type | Description |
|---|---|---|
session_id |
str
|
SHA-256 hex digest of the cookie value —
NOT the plaintext. The plaintext leaves over
|
user_id |
UUID
|
Owner of the session. |
created_at |
datetime
|
UTC timestamp when the session was issued. |
expires_at |
datetime
|
UTC timestamp after which the
session is rejected. Refreshed by
:meth: |
last_seen_at |
datetime
|
UTC timestamp of the last request that resolved the session. Updated by the middleware on every hit. |
ip |
str | None
|
Client IP recorded at session creation. Useful for the "list active sessions" UI. |
user_agent |
str | None
|
User-Agent header recorded at session creation. |
data |
dict[str, Any]
|
Arbitrary JSON-serializable bag — shopping cart id, last-seen route, locale preference, etc. |
SessionLoginSchema
¶
SessionResponseSchema
¶
Bases: BaseSchema
Body returned by POST /auth/session/login.
The session id itself is delivered via Set-Cookie —
deliberately NOT in this body — so JavaScript cannot read it
(HttpOnly cookies). The body carries everything the frontend
actually needs to render an authenticated state.
SessionSummarySchema
¶
Bases: BaseSchema
Public-safe projection of a :class:Session used by list endpoints.
Drops session_id (so revealing the list does NOT leak any
secret) and renames the visible identifier to id — a stable
UUID derived from the hashed session id by truncation, suitable
for DELETE /auth/session/{id} revocation calls.
tempest_fastapi_sdk.storage¶
AsyncMinIOClient
¶
AsyncMinIOClient(
endpoint: str,
access_key: str,
secret_key: str,
*,
default_bucket: str = "uploads",
secure: bool = False,
region: str = "us-east-1",
session_token: str | None = None,
)
Async-friendly facade over minio.Minio.
Use as an async context manager when you want explicit cleanup,
or hold a long-lived instance on the FastAPI app — the
underlying Minio client is thread-safe and reuses its
connection pool.
Example:
>>> from tempest_fastapi_sdk import AsyncMinIOClient
>>> storage = AsyncMinIOClient(
... endpoint="localhost:9000",
... access_key="minioadmin",
... secret_key="minioadmin",
... default_bucket="uploads",
... )
>>> await storage.ensure_bucket()
>>> await storage.put_object("hello.txt", b"world")
>>> body = await storage.get_object_bytes("hello.txt")
>>> assert body == b"world"
Initialize the client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
endpoint
|
str
|
|
required |
access_key
|
str
|
S3 access key. |
required |
secret_key
|
str
|
S3 secret key. |
required |
default_bucket
|
str
|
Bucket used by object operations
when no explicit |
'uploads'
|
secure
|
bool
|
Use HTTPS when |
False
|
region
|
str
|
S3 region. Match the bucket region for AWS S3; any value works for MinIO. |
'us-east-1'
|
session_token
|
str | None
|
Optional STS session token for temporary credentials. |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/storage/minio_client.py
bucket_exists
async
¶
Check whether bucket exists.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bucket
|
str | None
|
Target bucket; defaults to
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
bool
|
with the configured credentials. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
ensure_bucket
async
¶
Create the bucket if it does not exist yet.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bucket
|
str | None
|
Target bucket; defaults to
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
bool
|
when it already existed. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
list_buckets
async
¶
Return every bucket reachable with the current credentials.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Bucket names. Empty list when none exist. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
remove_bucket
async
¶
Delete an empty bucket.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bucket
|
str | None
|
Target bucket; defaults to
|
None
|
Raises:
| Type | Description |
|---|---|
S3Error
|
When the bucket is missing or non-empty. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
put_object
async
¶
put_object(
key: str,
data: bytes | BinaryIO,
*,
bucket: str | None = None,
content_type: str = "application/octet-stream",
metadata: dict[str, str] | None = None,
length: int | None = None,
part_size: int = 10 * 1024 * 1024,
) -> str
Upload an object.
Accepts both raw bytes and any binary file-like object
(open("file", "rb"), BytesIO, UploadFile.file).
For unknown-length streams pass length=-1 to enable
multipart upload via part_size chunks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Destination object key. |
required |
data
|
bytes | BinaryIO
|
Payload. |
required |
bucket
|
str | None
|
Override target bucket. |
None
|
content_type
|
str
|
MIME type. |
'application/octet-stream'
|
metadata
|
dict[str, str] | None
|
User metadata. Keys
are stored under the |
None
|
length
|
int | None
|
Payload size in bytes. Required
for unknown-length streams; computed automatically
when |
None
|
part_size
|
int
|
Chunk size for multipart upload (when
|
10 * 1024 * 1024
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
ETag of the uploaded object (quotes stripped). |
Raises:
| Type | Description |
|---|---|
S3Error
|
When the upload fails (auth, network, bucket missing, content rejected). |
Source code in tempest_fastapi_sdk/storage/minio_client.py
fput_object
async
¶
fput_object(
key: str,
file_path: str | Path,
*,
bucket: str | None = None,
content_type: str = "application/octet-stream",
metadata: dict[str, str] | None = None,
) -> str
Upload a file from disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Destination object key. |
required |
file_path
|
str | Path
|
Source path on disk. |
required |
bucket
|
str | None
|
Override target bucket. |
None
|
content_type
|
str
|
MIME type. |
'application/octet-stream'
|
metadata
|
dict[str, str] | None
|
User metadata. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
ETag of the uploaded object (quotes stripped). |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
When |
S3Error
|
When the upload fails. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
get_object_bytes
async
¶
Download an object as bytes.
Suitable for small objects. For large payloads prefer
:meth:stream_object to avoid loading everything in memory.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Object key. |
required |
bucket
|
str | None
|
Override source bucket. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
bytes |
bytes
|
Object payload. |
Raises:
| Type | Description |
|---|---|
S3Error
|
When the object is missing or the request fails. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
fget_object
async
¶
Download an object straight to disk.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Object key. |
required |
file_path
|
str | Path
|
Destination path. Parent directories are created if missing. |
required |
bucket
|
str | None
|
Override source bucket. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
The path the object was written to. |
Raises:
| Type | Description |
|---|---|
S3Error
|
When the object is missing or the request fails. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
stream_object
async
¶
stream_object(
key: str, *, bucket: str | None = None, chunk_size: int = 64 * 1024
) -> AsyncIterator[bytes]
Stream an object in fixed-size chunks.
The whole network read still runs in a worker thread —
each chunk_size read is one asyncio.to_thread
round-trip — but the event loop yields between chunks so
other requests progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Object key. |
required |
bucket
|
str | None
|
Override source bucket. |
None
|
chunk_size
|
int
|
Bytes per chunk. Default 64 KiB. |
64 * 1024
|
Returns:
| Type | Description |
|---|---|
AsyncIterator[bytes]
|
AsyncIterator[bytes]: Async generator yielding chunks |
AsyncIterator[bytes]
|
until the stream ends. |
Raises:
| Type | Description |
|---|---|
S3Error
|
When the object is missing or the request fails. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
stat_object
async
¶
stat_object(key: str, *, bucket: str | None = None) -> ObjectStat
Fetch metadata for an object without downloading it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Object key. |
required |
bucket
|
str | None
|
Override source bucket. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
ObjectStat |
ObjectStat
|
Subset of fields commonly needed by callers. |
ObjectStat
|
Use |
Raises:
| Type | Description |
|---|---|
S3Error
|
When the object is missing. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
list_objects
async
¶
List object keys under a prefix.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prefix
|
str
|
Prefix filter. Empty string returns everything. |
''
|
bucket
|
str | None
|
Override source bucket. |
None
|
recursive
|
bool
|
Walk into pseudo-directories.
|
True
|
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Object keys. Empty list when no matches — |
list[str]
|
matching the SDK convention of "no rows is not an |
list[str]
|
error". |
Source code in tempest_fastapi_sdk/storage/minio_client.py
remove_object
async
¶
Delete an object (or a specific version).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Object key. |
required |
bucket
|
str | None
|
Override target bucket. |
None
|
version_id
|
str | None
|
When the bucket has versioning enabled, the specific version to delete. |
None
|
Raises:
| Type | Description |
|---|---|
S3Error
|
When the delete fails for a reason other than "already gone" (deletes are idempotent on S3). |
Source code in tempest_fastapi_sdk/storage/minio_client.py
copy_object
async
¶
copy_object(
source_key: str,
dest_key: str,
*,
source_bucket: str | None = None,
dest_bucket: str | None = None,
) -> str
Copy an object inside the same store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_key
|
str
|
Source object key. |
required |
dest_key
|
str
|
Destination object key. |
required |
source_bucket
|
str | None
|
Source bucket; defaults to
|
None
|
dest_bucket
|
str | None
|
Destination bucket; defaults
to |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
ETag of the copied object (quotes stripped). |
Raises:
| Type | Description |
|---|---|
S3Error
|
When the source is missing or the copy fails. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
presigned_get_url
async
¶
presigned_get_url(
key: str, *, bucket: str | None = None, expires: timedelta = timedelta(hours=1)
) -> str
Generate a temporary download URL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Object key. |
required |
bucket
|
str | None
|
Override source bucket. |
None
|
expires
|
timedelta
|
URL lifetime. Maximum is 7 days (S3 hard limit). |
timedelta(hours=1)
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Pre-signed HTTPS URL that anyone with the link |
str
|
can |
Source code in tempest_fastapi_sdk/storage/minio_client.py
presigned_put_url
async
¶
presigned_put_url(
key: str, *, bucket: str | None = None, expires: timedelta = timedelta(minutes=15)
) -> str
Generate a temporary upload URL.
Lets the browser PUT directly to MinIO/S3 without the
bytes touching the FastAPI process — ideal for large
files.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
Destination object key. |
required |
bucket
|
str | None
|
Override target bucket. |
None
|
expires
|
timedelta
|
URL lifetime. Maximum is 7 days. |
timedelta(minutes=15)
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Pre-signed HTTPS URL accepting a |
str
|
object body until expiry. |
Source code in tempest_fastapi_sdk/storage/minio_client.py
ObjectStat
dataclass
¶
ObjectStat(
bucket: str,
key: str,
size: int,
etag: str | None,
content_type: str | None,
last_modified: datetime | None,
metadata: dict[str, str],
raw: Object,
)
Subset of object metadata returned by :meth:AsyncMinIOClient.stat_object.
The full minio.datatypes.Object instance is also reachable via
the raw attribute when you need the long tail of fields
(version id, owner, restoration state, etc.).
Attributes:
| Name | Type | Description |
|---|---|---|
bucket |
str
|
Bucket the object lives in. |
key |
str
|
Object key (S3 path). |
size |
int
|
Size in bytes. |
etag |
str | None
|
Server-side ETag (quotes stripped). |
content_type |
str | None
|
MIME type recorded at upload. |
last_modified |
datetime | None
|
Last modification timestamp in UTC. |
metadata |
dict[str, str]
|
User metadata keyed without the
|
raw |
Object
|
Underlying |
Alembic hooks¶
reorder_base_columns_first
¶
reorder_base_columns_first(
context: MigrationContext, revision: Any, directives: list[Any]
) -> None
Reorder columns inside every autogenerated CreateTableOp.
SQLAlchemy stores columns in declaration order, which already
matches what BaseModel defines (id →
updated_at). Autogenerate normally honors that, but
third-party mixins or class composition can flip the order in
ways that leak into the migration. This hook normalizes it:
- The four
BaseModelcolumns (id,is_active,created_at,updated_at) come first, in that exact order. - Constraints stay where Alembic placed them (typically right after the columns they reference).
- Every other column keeps its original relative position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
context
|
MigrationContext
|
The active Alembic context. |
required |
revision
|
Any
|
The new revision identifier — unused. |
required |
directives
|
list[Any]
|
The list of |
required |
Source code in tempest_fastapi_sdk/db/alembic_hooks.py
compose_hooks
¶
Chain multiple process_revision_directives hooks.
Hooks run in the order they're passed and share the same
directives list — each can mutate it before the next sees it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
*hooks
|
ProcessRevisionDirectives
|
Callables matching the Alembic signature. |
()
|
Returns:
| Name | Type | Description |
|---|---|---|
ProcessRevisionDirectives |
ProcessRevisionDirectives
|
A single callable Alembic can |
ProcessRevisionDirectives
|
register. |
Source code in tempest_fastapi_sdk/db/alembic_hooks.py
Settings¶
tempest_fastapi_sdk.settings¶
BaseAppSettings
¶
Bases: BaseSettings
Shared configuration for Settings classes across projects.
Provides the canonical pydantic-settings config block; concrete projects subclass this and add their domain-specific fields (database URLs, secrets, third-party keys, etc.).
The defaults:
env_file=".env"— load environment variables from a local.envfile when present.extra="ignore"— silently drop unexpected env vars instead of raising at startup.case_sensitive=True— env var names are matched exactly.frozen=True— settings are immutable after construction.str_strip_whitespace=True— trim accidental whitespace around env values.from_attributes=True— allow building from objects with attribute access (rarely needed for settings, but harmless).
Attributes:
| Name | Type | Description |
|---|---|---|
model_config |
SettingsConfigDict
|
The pydantic-settings configuration. |
mixins
¶
Composable settings mixins covering common service dependencies.
Each mixin is a fully-typed Pydantic model with sensible defaults so
projects can opt in by listing the mixins they need alongside their
own concrete Settings class:
class Settings(DatabaseSettings, RedisSettings, BaseAppSettings):
...
The mixins MUST be placed before :class:BaseAppSettings in the MRO
so the latter's model_config wins. None of the mixins reads
environment variables on their own — they rely on the consumer's
BaseAppSettings configuration.
Every field carries title, description and examples so
JSON-Schema consumers (FastAPI /docs, /redoc, IDE tooling,
pydantic.model_json_schema()) render rich metadata out of the
box.
ServerSettings
¶
Bases: BaseSettings
HTTP server bind configuration.
LogSettings
¶
Bases: BaseSettings
Structured logging configuration.
DatabaseSettings
¶
Bases: BaseSettings
SQLAlchemy database connection configuration.
RedisSettings
¶
Bases: BaseSettings
Redis connection configuration.
RabbitMQSettings
¶
Bases: BaseSettings
RabbitMQ / FastStream broker configuration.
JWTSettings
¶
Bases: BaseSettings
JWT signing and verification configuration.
CORSSettings
¶
Bases: BaseSettings
CORS middleware configuration.
.. warning::
The default CORS_ORIGINS=["*"] is permissive on purpose
so local development works out of the box. Never ship
this default to production — set CORS_ORIGINS to the
explicit list of trusted frontend origins. "*" is also
incompatible with CORS_ALLOW_CREDENTIALS=True (browsers
ignore credentialed requests sent to a wildcard origin).
EmailSettings
¶
Bases: BaseSettings
SMTP / transactional email configuration.
Mirrors the constructor arguments of
:class:tempest_fastapi_sdk.EmailUtils so a service can wire it up
with EmailUtils(**settings.email_kwargs()).
UploadSettings
¶
Bases: BaseSettings
File upload constraints.
Mirrors the constructor arguments of
:class:tempest_fastapi_sdk.UploadUtils.
TokenSettings
¶
Bases: BaseSettings
Shared-secret X-Token configuration.
Used by :func:tempest_fastapi_sdk.make_token_dependency for
internal service-to-service authentication. Validation is performed
with :func:hmac.compare_digest.
WebPushSettings
¶
Bases: BaseSettings
Web Push / VAPID configuration.
Mirrors the constructor arguments of
:class:tempest_fastapi_sdk.WebPushDispatcher.
TaskIQSettings
¶
Bases: BaseSettings
TaskIQ broker / result backend configuration.
Use this when the TaskIQ broker is not the same RabbitMQ /
Redis instance covered by :class:RabbitMQSettings /
:class:RedisSettings.
AuthSettings
¶
Bases: BaseSettings
Configuration for the bundled signup / activation / reset flows.
Consumed by :class:tempest_fastapi_sdk.auth.UserAuthService
and :func:tempest_fastapi_sdk.make_auth_router. Each flag
has a sensible production default; flip AUTH_AUTO_ACTIVATE
or AUTH_RETURN_TOKEN_IN_RESPONSE only in dev / CI.
MinIOSettings
¶
Bases: BaseSettings
MinIO / S3-compatible object storage configuration.
Consumed by :class:tempest_fastapi_sdk.AsyncMinIOClient. The
same shape works for any S3-compatible target (AWS S3, MinIO,
Backblaze B2, Cloudflare R2, Wasabi, DigitalOcean Spaces).
SessionSettings
¶
Bases: BaseSettings
Server-side session cookie + storage configuration.
Consumed by :class:tempest_fastapi_sdk.SessionAuth,
:class:tempest_fastapi_sdk.SessionMiddleware, and
:func:tempest_fastapi_sdk.make_session_router. Defaults assume
HTTPS in production (SESSION_COOKIE_SECURE=True) and a
same-site SaaS topology (SESSION_COOKIE_SAMESITE="lax") —
relax both only for local HTTP development.
WebSocketSettings
¶
Bases: BaseSettings
WebSocket router configuration.
Consumed by :func:tempest_fastapi_sdk.make_websocket_router and
:class:tempest_fastapi_sdk.WebSocketHub. Defaults are tuned for
typical browser ↔ FastAPI deployments — heartbeats every 30s,
drop after 60s without pong, five concurrent connections per
user.
Admin¶
tempest_fastapi_sdk.admin¶
AdminSite
¶
AdminSite(
title: str = "Admin",
*,
index_subtitle: str = "Site administration",
site_url: str | None = None,
)
Holds the set of :class:AdminModel configurations to expose.
Each project instantiates one site, registers its admin
configurations, and passes the site to :func:make_admin_router.
Sites are explicit (no auto-discovery) so the surface remains
predictable across deployments.
Attributes:
| Name | Type | Description |
|---|---|---|
title |
str
|
Branding shown at the top of every admin page. |
index_subtitle |
str
|
Optional subtitle for the dashboard. |
site_url |
str | None
|
Optional "View site" link rendered in the admin header. |
Initialize the site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
title
|
str
|
Branding text. |
'Admin'
|
index_subtitle
|
str
|
Dashboard subtitle. |
'Site administration'
|
site_url
|
str | None
|
Optional outbound link rendered in the admin header. |
None
|
Source code in tempest_fastapi_sdk/admin/site.py
registry
property
¶
registry: dict[str, AdminModel[Any]]
Return a copy of the slug→admin mapping.
Returns:
| Type | Description |
|---|---|
dict[str, AdminModel[Any]]
|
dict[str, AdminModel[Any]]: The current registry. |
register
¶
register(admin: AdminModel[Any]) -> AdminModel[Any]
Register admin against its model slug.
Example::
site.register(AdminModel(model=UserModel))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
admin
|
AdminModel[Any]
|
The admin configuration instance. |
required |
Returns:
| Type | Description |
|---|---|
AdminModel[Any]
|
AdminModel[Any]: The same instance (so the call can be |
AdminModel[Any]
|
chained or assigned). |
Raises:
| Type | Description |
|---|---|
ValueError
|
When another admin is already registered under the same slug. |
Source code in tempest_fastapi_sdk/admin/site.py
unregister
¶
Remove a previously registered admin.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slug
|
str
|
The slug to drop. |
required |
Raises:
| Type | Description |
|---|---|
KeyError
|
When no admin is registered under |
get
¶
get(slug: str) -> AdminModel[Any] | None
Return the admin registered under slug, or None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slug
|
str
|
The admin slug. |
required |
Returns:
| Type | Description |
|---|---|
AdminModel[Any] | None
|
AdminModel[Any] | None: The configuration instance. |
Source code in tempest_fastapi_sdk/admin/site.py
require
¶
require(slug: str) -> AdminModel[Any]
Return the admin registered under slug or raise.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
slug
|
str
|
The admin slug. |
required |
Returns:
| Type | Description |
|---|---|
AdminModel[Any]
|
AdminModel[Any]: The configuration instance. |
Raises:
| Type | Description |
|---|---|
KeyError
|
When no admin matches the slug. |
Source code in tempest_fastapi_sdk/admin/site.py
iter_models
¶
iter_models() -> list[AdminModel[Any]]
Return registered admins ordered by display name.
Returns:
| Type | Description |
|---|---|
list[AdminModel[Any]]
|
list[AdminModel[Any]]: Ordered admin instances. |
Source code in tempest_fastapi_sdk/admin/site.py
AdminModel
¶
AdminModel(
model: type[ModelT],
*,
list_display: Sequence[FieldRef] | None = None,
list_filter: Sequence[FieldRef] = (),
search_fields: Sequence[FieldRef] = (),
readonly_fields: Sequence[FieldRef] = (),
ordering: OrderRef | None = None,
page_size: int = 25,
identity_field: FieldRef = "id",
repository_class: type[BaseRepository[Any]] | None = None,
verbose_name: str | None = None,
verbose_name_plural: str | None = None,
)
Bases: Generic[ModelT]
Declarative admin configuration for one SQLAlchemy model.
Instantiate once per managed model and pass it to
:meth:AdminSite.register. Unlike Django's class-based ModelAdmin,
this is a plain typed instance — the constructor signature is the
contract, fields accept real SQLAlchemy column attributes (so typos
surface in the editor, not at runtime), and there is no metaclass
magic::
site.register(AdminModel(
model=UserModel,
list_display=[UserModel.email, UserModel.is_admin],
search_fields=[UserModel.email],
ordering=desc(UserModel.created_at),
))
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
type[ModelT]
|
The SQLAlchemy model class. |
required |
list_display
|
Sequence[FieldRef] | None
|
Columns shown in the
list view. |
None
|
list_filter
|
Sequence[FieldRef]
|
Fields surfaced as filter dropdowns; matched via the repository's standard filter pipeline. |
()
|
search_fields
|
Sequence[FieldRef]
|
String columns searched with
|
()
|
readonly_fields
|
Sequence[FieldRef]
|
Fields locked in the detail view. |
()
|
ordering
|
OrderRef | None
|
Default ordering. Accepts a column
(ascending), |
None
|
page_size
|
int
|
Default rows per page in the list view. |
25
|
identity_field
|
FieldRef
|
Column used to look up a single row
from the detail URL. Defaults to |
'id'
|
repository_class
|
type[BaseRepository[Any]] | None
|
Concrete
repository. |
None
|
verbose_name
|
str | None
|
Singular display name; defaults to the model name humanized. |
None
|
verbose_name_plural
|
str | None
|
Plural display name; defaults to
|
None
|
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
Build and validate the configuration. See class docstring.
Source code in tempest_fastapi_sdk/admin/config.py
get_verbose_name
¶
Return the configured (or auto-derived) singular display name.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The display name. |
Source code in tempest_fastapi_sdk/admin/config.py
get_verbose_name_plural
¶
Return the configured (or auto-derived) plural display name.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The plural display name. |
Source code in tempest_fastapi_sdk/admin/config.py
get_slug
¶
Return the URL slug under which the model is exposed.
Defaults to __tablename__ so admin URLs and DB tables
stay in sync.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The slug. |
Source code in tempest_fastapi_sdk/admin/config.py
column_names
¶
Return every mapped column name on :attr:model.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Column keys in declaration order. |
resolved_list_display
¶
Return the effective list_display column list.
Defaults to every column except hashed_password when
unconfigured.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: The list of columns to render. |
Source code in tempest_fastapi_sdk/admin/config.py
build_repository
¶
build_repository(session: AsyncSession) -> BaseRepository[ModelT]
Instantiate the repository for session.
Uses :attr:repository_class when provided (typically a subclass
adding custom queries), otherwise instantiates :class:BaseRepository
directly with model=self.model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
The DB session to bind. |
required |
Returns:
| Type | Description |
|---|---|
BaseRepository[ModelT]
|
BaseRepository[ModelT]: A repository ready to use. |
Source code in tempest_fastapi_sdk/admin/config.py
AdminAuthBackend
¶
Bases: ABC
Abstract base for admin authentication.
Implementations receive a session-bound async DB session per
login attempt; the default :class:UserModelAuthBackend queries
a :class:BaseUserModel subclass and enforces is_admin=True.
Custom backends can use the same protocol to integrate LDAP,
OAuth, IAM tokens, etc.
authenticate
abstractmethod
async
¶
Verify credentials and return the authenticated principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
A live DB session. |
required |
identifier
|
str
|
The login identifier (typically email). |
required |
password
|
str
|
The plaintext password. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Any |
Any
|
The authenticated principal. The admin router calls |
Any
|
meth: |
|
Any
|
session payload. Typically a :class: |
Raises:
| Type | Description |
|---|---|
AdminAuthError
|
On any rejection (unknown user, wrong password, not an admin, disabled account). |
Source code in tempest_fastapi_sdk/admin/auth.py
load_principal
abstractmethod
async
¶
Reload the principal from storage given its ID.
Called on every request once the session cookie has been
validated. Returning None invalidates the session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
A live DB session. |
required |
principal_id
|
str
|
The identifier produced by
:meth: |
required |
Returns:
| Type | Description |
|---|---|
Any | None
|
Any | None: The reloaded principal, or |
Any | None
|
no longer exists or no longer has admin access. |
Source code in tempest_fastapi_sdk/admin/auth.py
principal_id
abstractmethod
¶
Return a stable identifier for the authenticated principal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Any
|
The value returned by :meth: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The identifier serialized into the session cookie. |
Source code in tempest_fastapi_sdk/admin/auth.py
display_name
¶
Return a human-readable label for the principal.
Defaults to the principal's email attribute (or its repr
when missing); override for richer labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Any
|
The principal. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
A label suitable for the admin header bar. |
Source code in tempest_fastapi_sdk/admin/auth.py
UserModelAuthBackend
¶
UserModelAuthBackend(user_model: type[BaseUserModel])
Bases: AdminAuthBackend
Default backend backed by :class:BaseUserModel.
Authenticates by selecting the row whose email matches the
inbound identifier (case-insensitive), verifying the password via
:class:tempest_fastapi_sdk.PasswordUtils and enforcing both
is_admin=True and is_active=True. The
:attr:last_login_at column is stamped on every successful login.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
The concrete model class.
Must be a subclass of :class: |
required |
Initialize the backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_model
|
type[BaseUserModel]
|
The user model to query. |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
When |
Source code in tempest_fastapi_sdk/admin/auth.py
authenticate
async
¶
authenticate(session: AsyncSession, *, identifier: str, password: str) -> BaseUserModel
Verify credentials against the configured user model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
A live DB session. |
required |
identifier
|
str
|
The user's email. |
required |
password
|
str
|
The plaintext password. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
BaseUserModel |
BaseUserModel
|
The authenticated row with |
BaseUserModel
|
attr: |
Raises:
| Type | Description |
|---|---|
AdminAuthError
|
On any rejection. |
Source code in tempest_fastapi_sdk/admin/auth.py
load_principal
async
¶
load_principal(session: AsyncSession, principal_id: str) -> BaseUserModel | None
Reload the user by ID, ensuring they still qualify for admin.
Returns None when the user no longer exists, has been
soft-deleted, or had is_admin revoked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session
|
AsyncSession
|
A live DB session. |
required |
principal_id
|
str
|
The UUID hex string from the cookie. |
required |
Returns:
| Type | Description |
|---|---|
BaseUserModel | None
|
BaseUserModel | None: The user row, or |
Source code in tempest_fastapi_sdk/admin/auth.py
principal_id
¶
Return the id UUID hex for serialization.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
principal
|
Any
|
A :class: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The UUID as |
Source code in tempest_fastapi_sdk/admin/auth.py
make_admin_router
¶
make_admin_router(
site: AdminSite,
*,
db: AsyncDatabaseManager,
auth_backend: AdminAuthBackend,
secret_key: str,
prefix: str = "/admin",
session_store: SessionStore | None = None,
cookie_secure: bool = True,
) -> APIRouter
Build the FastAPI router that mounts the admin site.
Routes attached:
GET {prefix}/login— login form.POST {prefix}/login— login submit.POST {prefix}/logout— clear session + redirect.GET {prefix}/— dashboard listing registered admins.GET {prefix}/m/{slug}/— list view (paginated).GET {prefix}/m/{slug}/{identity}— detail view.- Static files under
{prefix}/staticnamedadmin_static.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
site
|
AdminSite
|
The configured registry. |
required |
db
|
AsyncDatabaseManager
|
Active DB manager (used for both sessions and the readiness check). |
required |
auth_backend
|
AdminAuthBackend
|
Backend resolving login credentials to a principal. |
required |
secret_key
|
str
|
Secret used to sign the session cookie. 32 bytes minimum. |
required |
prefix
|
str
|
URL prefix; defaults to |
'/admin'
|
session_store
|
SessionStore | None
|
Override the default
:class: |
None
|
cookie_secure
|
bool
|
Set the |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
A router ready to attach via |
Source code in tempest_fastapi_sdk/admin/router.py
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 | |
Cache¶
AsyncRedisManager
¶
Manage the lifecycle of a single async Redis client.
Mirrors the public surface of
:class:tempest_fastapi_sdk.AsyncDatabaseManager so application
bootstrapping stays uniform across backends. The actual client is
created on first :meth:connect call; in-process callers can use
:meth:get_client_context from a FastAPI dependency or any async
context manager.
Attributes:
| Name | Type | Description |
|---|---|---|
url |
str
|
The Redis connection URL. |
decode_responses |
bool
|
Whether the underlying client
decodes responses to |
Initialize the manager (no connection opened yet).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url
|
str
|
The Redis URL ( |
required |
decode_responses
|
bool
|
Whether to decode bytes to strings on every command. |
True
|
**client_kwargs
|
Any
|
Extra kwargs forwarded to
|
{}
|
Source code in tempest_fastapi_sdk/cache/redis_manager.py
client
property
¶
Return the live client.
Returns:
| Name | Type | Description |
|---|---|---|
Redis |
Redis
|
The connected Redis client. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
When :meth: |
connect
async
¶
Open the underlying Redis client.
Safe to call multiple times — subsequent calls are no-ops while the same client is alive.
Source code in tempest_fastapi_sdk/cache/redis_manager.py
disconnect
async
¶
Close the underlying client and release its connection pool.
get_client_context
async
¶
Yield the live client inside an async with block.
The manager owns the lifecycle — exiting the context does
NOT close the underlying client. Use :meth:disconnect
during application shutdown instead.
Yields:
| Name | Type | Description |
|---|---|---|
Redis |
AsyncIterator[Redis]
|
The connected client. |
Source code in tempest_fastapi_sdk/cache/redis_manager.py
client_dependency
async
¶
Async generator dependency suitable for FastAPI Depends.
Yields:
| Name | Type | Description |
|---|---|---|
Redis |
AsyncIterator[Redis]
|
The connected client. |
health_check
async
¶
Return True when PING succeeds.
Errors are caught and logged at WARNING level — the health router treats exceptions as a failed check.
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Source code in tempest_fastapi_sdk/cache/redis_manager.py
Server-Sent Events¶
tempest_fastapi_sdk.sse¶
EventStream
¶
Async in-memory queue feeding one SSE HTTP connection.
A handler builds one stream per client request, publish-es
events from anywhere in the application (background tasks,
websockets, dependency callbacks), and passes :meth:stream to
:func:sse_response. A None enqueued by :meth:close
terminates the iteration so the response completes cleanly.
Heartbeats are emitted as SSE comments
(: keepalive lines) when the queue stays empty for longer
than heartbeat_seconds; this keeps load-balancers from
closing idle TCP connections.
Attributes:
| Name | Type | Description |
|---|---|---|
heartbeat_seconds |
float | None
|
Idle interval that triggers
a comment heartbeat. |
Initialize the stream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
heartbeat_seconds
|
float | None
|
Idle interval before a comment heartbeat is emitted. |
15.0
|
Source code in tempest_fastapi_sdk/sse/event_stream.py
publish
async
¶
publish(
data: Any = "",
*,
event: str | None = None,
id: str | None = None,
retry: int | None = None,
) -> None
Enqueue a new event for delivery.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
Any
|
The payload (string, bytes or JSON-serializable). |
''
|
event
|
str | None
|
Optional event name. |
None
|
id
|
str | None
|
Optional Last-Event-ID. |
None
|
retry
|
int | None
|
Optional reconnect hint in milliseconds. |
None
|
Source code in tempest_fastapi_sdk/sse/event_stream.py
publish_event
async
¶
publish_event(event: ServerSentEvent) -> None
Enqueue a pre-built :class:ServerSentEvent.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
event
|
ServerSentEvent
|
The event to enqueue. |
required |
close
async
¶
stream
async
¶
Yield encoded SSE bytes until :meth:close is invoked.
Yields:
| Name | Type | Description |
|---|---|---|
bytes |
AsyncIterator[bytes]
|
An encoded SSE frame ready to write to the wire. |
Source code in tempest_fastapi_sdk/sse/event_stream.py
ServerSentEvent
dataclass
¶
ServerSentEvent(
data: Any = "",
event: str | None = None,
id: str | None = None,
retry: int | None = None,
comment: str | None = None,
)
A single SSE frame.
Encodes to the line-based wire format defined by the spec
(https://html.spec.whatwg.org/multipage/server-sent-events.html).
data may be a string, bytes, or any JSON-serializable Python
object — non-string payloads are JSON-encoded before transmission.
Attributes:
| Name | Type | Description |
|---|---|---|
data |
Any
|
The event payload. |
event |
str | None
|
Optional event name; the browser routes
|
id |
str | None
|
Optional |
retry |
int | None
|
Reconnection delay (milliseconds) the browser should use after a connection drop. |
comment |
str | None
|
Optional comment line prepended to the
frame (renders as |
encode
¶
Render the event as the wire-format string.
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The encoded event, including the trailing blank line |
str
|
that marks frame boundaries. |
Source code in tempest_fastapi_sdk/sse/event_stream.py
sse_response
¶
sse_response(
stream: AsyncIterable[bytes],
*,
status_code: int = 200,
headers: dict[str, str] | None = None,
) -> StreamingResponse
Wrap stream in a Starlette text/event-stream response.
Adds the SSE-specific headers (Cache-Control: no-cache,
Connection: keep-alive, X-Accel-Buffering: no) so
intermediate proxies don't buffer or cache the long-lived
response. Caller-supplied headers are layered below the
SSE defaults so the three critical headers above cannot be
accidentally overridden — pass extra metadata, not replacements.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stream
|
AsyncIterable[bytes]
|
The byte stream produced by
:meth: |
required |
status_code
|
int
|
HTTP status code. Defaults to |
200
|
headers
|
dict[str, str] | None
|
Extra headers to attach. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
StreamingResponse |
StreamingResponse
|
A ready-to-return SSE response. |
Source code in tempest_fastapi_sdk/sse/event_stream.py
WebSocket¶
tempest_fastapi_sdk.websockets¶
WebSocketHub
¶
In-process registry of live WebSocket connections.
Tracks every connection accepted by
:func:tempest_fastapi_sdk.make_websocket_router and offers three
delivery patterns:
send_to(user_id, envelope)— every socket the user has open right now.broadcast(envelope, topic=...)— every subscriber oftopic(or every connection whentopicis omitted).subscribe(connection_id, topic)/unsubscribe(connection_id, topic)— per-connection topic membership.
This hub is single-process. For multi-replica deployments, fan
out across processes via a pub/sub backend (Redis pub/sub,
RabbitMQ topic exchange) — the hub itself only handles
in-process delivery. The future RedisWebSocketHub will swap
the local broadcast for a redis-driven one without changing the
public surface; today, run a single replica or use sticky
sessions when WebSocket fan-out matters.
The hub is safe to share across handlers in the same FastAPI
app — all mutators take an asyncio.Lock so concurrent
register/unregister calls do not corrupt the internal state.
Initialize the hub.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_per_user
|
int
|
Cap on concurrent connections per
user. When the cap is hit on |
5
|
Source code in tempest_fastapi_sdk/websockets/hub.py
register
async
¶
register(user_id: UUID, ws: WebSocket) -> WebSocketConnection
Register an accepted WebSocket against user_id.
When the user is already at max_per_user open
connections, the oldest one is force-closed (code 4429)
before the new connection is admitted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
The authenticated user owning the new connection. |
required |
ws
|
WebSocket
|
The accepted FastAPI WebSocket. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
WebSocketConnection |
WebSocketConnection
|
The registered handle. Pass its |
WebSocketConnection
|
|
|
WebSocketConnection
|
meth: |
Source code in tempest_fastapi_sdk/websockets/hub.py
unregister
async
¶
Remove a connection from every index. Idempotent.
subscribe
async
¶
Add topic to the connection's subscription set.
Source code in tempest_fastapi_sdk/websockets/hub.py
unsubscribe
async
¶
Drop topic from the connection's subscription set.
Source code in tempest_fastapi_sdk/websockets/hub.py
send_to
async
¶
send_to(user_id: UUID, envelope: WSEnvelope) -> int
Send envelope to every connection owned by user_id.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
user_id
|
UUID
|
Target user. |
required |
envelope
|
WSEnvelope
|
Frame to deliver. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Number of sockets that successfully received the |
int
|
frame. Dead connections are evicted transparently and do |
|
int
|
not count. |
Source code in tempest_fastapi_sdk/websockets/hub.py
broadcast
async
¶
broadcast(envelope: WSEnvelope, *, topic: str | None = None) -> int
Deliver envelope to every subscriber of topic.
When topic is None, the envelope is delivered to
every active connection across every user — useful for
system-wide announcements, but expensive at scale; prefer
topic-scoped delivery when you can.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
envelope
|
WSEnvelope
|
Frame to deliver. |
required |
topic
|
str | None
|
Topic to fan out on, or |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
Number of successful sends. See :meth: |
Source code in tempest_fastapi_sdk/websockets/hub.py
online_users
¶
connection_count
¶
WebSocketConnection
dataclass
¶
A single live WebSocket bound to an authenticated user.
Attributes:
| Name | Type | Description |
|---|---|---|
connection_id |
UUID
|
Unique identifier; the hub keys connections by this so the same user can hold several sockets at once (e.g. multi-tab). |
user_id |
UUID
|
The user the connection belongs to. Set by
|
ws |
WebSocket
|
The underlying FastAPI/Starlette socket. |
topics |
set[str]
|
Set of topic strings the connection has
subscribed to. Populated by
:meth: |
make_websocket_router
¶
make_websocket_router(
handler: WSHandler,
*,
hub: WebSocketHub,
bearer_resolver: BearerResolver,
settings: WebSocketSettings,
path: str = "/ws",
tags: list[str] | None = None,
) -> APIRouter
Build a single-endpoint WebSocket router.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
handler
|
WSHandler
|
Coroutine the SDK invokes once per authenticated connection. The handler is responsible for the message loop; the router takes care of auth + heartbeat + hub registration. |
required |
hub
|
WebSocketHub
|
Shared hub for broadcast / send_to. One hub instance per FastAPI app is the usual setup. |
required |
bearer_resolver
|
BearerResolver
|
Awaitable returning the
user UUID for a token, or |
required |
settings
|
WebSocketSettings
|
Heartbeat / cap / size limits. |
required |
path
|
str
|
Mount path. Defaults to |
'/ws'
|
tags
|
list[str] | None
|
OpenAPI tags. Defaults to
|
None
|
Returns:
| Name | Type | Description |
|---|---|---|
APIRouter |
APIRouter
|
Ready to mount with |
Source code in tempest_fastapi_sdk/websockets/router.py
WSEnvelope
¶
Bases: BaseSchema
Canonical message envelope for the bundled WebSocket router.
Every frame the SDK sends — application data, heartbeats, errors —
fits this shape so clients can dispatch on type alone. Senders
on the consumer side are encouraged to use it too, but the router
accepts any JSON payload and only the heartbeat frames it owns are
strictly required to follow this schema.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
str
|
Event name. Reserved values:
|
data |
dict[str, Any]
|
Payload — empty dict when none. |
request_id |
str | None
|
Echoes the originating HTTP request-id for end-to-end tracing across SSE/HTTP/WS. |
Web Push¶
tempest_fastapi_sdk.webpush¶
WebPushDispatcher
¶
WebPushDispatcher(
vapid_private_key: str,
*,
vapid_subject: str,
ttl_seconds: int = 60,
extra_vapid_claims: dict[str, str] | None = None,
)
Send VAPID-signed Web Push notifications to browser subscribers.
Wraps the synchronous pywebpush library in
:func:asyncio.to_thread so dispatch fits the SDK's async-first
convention. Subscriptions that respond with 404/410 raise
:class:WebPushGoneError so the caller can prune their store;
every other failure raises :class:WebPushError.
Attributes:
| Name | Type | Description |
|---|---|---|
vapid_private_key |
str
|
VAPID private key (PEM or base64url encoded). MUST match the public key advertised to clients. |
vapid_claims |
dict[str, str]
|
Mandatory JWT claims attached
to every push. |
ttl_seconds |
int
|
Default time-to-live applied to each push (the push service buffers the payload for at most this long when the device is offline). |
Initialize the dispatcher.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
vapid_private_key
|
str
|
VAPID private key. |
required |
vapid_subject
|
str
|
The |
required |
ttl_seconds
|
int
|
Default TTL for delivered messages. |
60
|
extra_vapid_claims
|
dict[str, str] | None
|
Additional claims merged into the JWT. |
None
|
Source code in tempest_fastapi_sdk/webpush/dispatcher.py
send
async
¶
send(
subscription: WebPushSubscriptionSchema,
payload: WebPushPayloadSchema | dict[str, Any] | str | bytes,
*,
ttl_seconds: int | None = None,
headers: dict[str, str] | None = None,
) -> None
Send a single push notification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subscription
|
WebPushSubscriptionSchema
|
The recipient. |
required |
payload
|
WebPushPayloadSchema | dict | str | bytes
|
The notification body. Pydantic models and dicts are JSON-encoded; strings/bytes are sent as-is. |
required |
ttl_seconds
|
int | None
|
Override
:attr: |
None
|
headers
|
dict[str, str] | None
|
Extra HTTP headers to attach to the push request (forwarded to pywebpush). |
None
|
Raises:
| Type | Description |
|---|---|
WebPushGoneError
|
When the push service returns 404/410. |
WebPushError
|
For any other delivery failure. |
Source code in tempest_fastapi_sdk/webpush/dispatcher.py
send_many
async
¶
send_many(
subscriptions: list[WebPushSubscriptionSchema],
payload: WebPushPayloadSchema | dict[str, Any] | str | bytes,
*,
ttl_seconds: int | None = None,
headers: dict[str, str] | None = None,
) -> list[str]
Fan out a single payload to many subscriptions.
Each dispatch runs concurrently via :func:asyncio.gather.
Subscriptions that respond with 404/410 are returned so the
caller can prune them; every other failure is logged and
also returned in the gone list when the endpoint is known.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
subscriptions
|
list[WebPushSubscriptionSchema]
|
Recipients. |
required |
payload
|
WebPushPayloadSchema | dict[str, Any] | str | bytes
|
The notification body (same shapes as :meth: |
required |
ttl_seconds
|
int | None
|
Override TTL. |
None
|
headers
|
dict[str, str] | None
|
Extra HTTP headers. |
None
|
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: Endpoints whose subscription is gone and should |
list[str]
|
be removed from the application's store. |
Source code in tempest_fastapi_sdk/webpush/dispatcher.py
WebPushSubscriptionSchema
¶
Bases: BaseSchema
Server-side representation of PushSubscription.toJSON().
Two browser-flavored field names (expirationTime) are exposed
via aliases so the schema round-trips JSON produced by
JSON.stringify(subscription) without manual key mangling.
Attributes:
| Name | Type | Description |
|---|---|---|
endpoint |
str
|
Push service endpoint URL. |
keys |
WebPushKeysSchema
|
Encryption keys. |
expiration_time |
int | None
|
Optional expiration timestamp
in milliseconds since epoch. Aliased to |
WebPushPayloadSchema
¶
Bases: BaseSchema
Optional helper for the JSON payload delivered with each push.
Mirrors the Notification API options exposed in service workers; callers that want stricter typing can subclass this for their application-specific event types.
Attributes:
| Name | Type | Description |
|---|---|---|
title |
str | None
|
Notification title shown to the user. |
body |
str | None
|
Notification body. |
icon |
str | None
|
URL of the icon to display. |
badge |
str | None
|
URL of the badge icon (Android). |
tag |
str | None
|
Tag used to coalesce notifications. |
data |
dict[str, Any] | None
|
Arbitrary application payload. |
actions |
list[dict[str, Any]] | None
|
Action button specs. |
Utils¶
tempest_fastapi_sdk.utils¶
PasswordUtils
¶
Hash and verify passwords using bcrypt.
Stateless utility — instantiate once and reuse across the
application. The cost factor (rounds) controls how slow
hashing is; 12 is a sensible 2026 default. Raise it when CPU
budget allows to keep up with hardware.
Attributes:
| Name | Type | Description |
|---|---|---|
rounds |
int
|
The bcrypt cost factor. |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rounds
|
int
|
The bcrypt cost factor. Higher values make
hashing slower and brute-force attacks harder.
Defaults to |
12
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/password.py
hash
¶
Hash a plaintext password.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plain
|
str
|
The plaintext password. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The bcrypt hash encoded as a UTF-8 string, ready to |
str
|
persist in a database column. |
Source code in tempest_fastapi_sdk/utils/password.py
verify
¶
Verify a plaintext password against an existing hash.
Catches malformed hashes and returns False rather than
raising, so callers can branch on the boolean without
bcrypt-specific error handling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
plain
|
str
|
The plaintext password to verify. |
required |
hashed
|
str
|
The previously stored bcrypt hash. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
Source code in tempest_fastapi_sdk/utils/password.py
JWTUtils
¶
JWTUtils(
secret: str,
*,
algorithm: str = "HS256",
default_ttl: timedelta = timedelta(hours=1),
issuer: str | None = None,
)
Encode and decode JWTs using a shared secret.
Every token gets an iat (issued-at) and exp (expiry)
claim populated automatically; the caller is responsible for the
rest (sub, custom claims, etc.). When the helper is created
with issuer=, the iss claim is also added on encode and
verified on decode.
Attributes:
| Name | Type | Description |
|---|---|---|
algorithm |
str
|
The JWT signing algorithm. |
default_ttl |
timedelta
|
Default expiration applied on
:meth: |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
secret
|
str
|
The signing key (HMAC) or private key (RSA/EC). |
required |
algorithm
|
str
|
JWT algorithm. Defaults to |
'HS256'
|
default_ttl
|
timedelta
|
TTL applied by :meth: |
timedelta(hours=1)
|
issuer
|
str | None
|
Value for the |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/jwt.py
encode
¶
Encode payload as a signed JWT.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
payload
|
dict[str, Any]
|
Claims to include. Typically
contains a stable subject ( |
required |
ttl
|
timedelta | None
|
Override :attr: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
The compact-serialized JWT. |
Source code in tempest_fastapi_sdk/utils/jwt.py
decode
¶
Decode and verify a JWT.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
The token to decode. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: The decoded claims. |
Raises:
| Type | Description |
|---|---|
ExpiredTokenException
|
When the |
InvalidTokenException
|
For every other validation failure (bad signature, wrong issuer, missing claim, malformed payload, etc.). |
Source code in tempest_fastapi_sdk/utils/jwt.py
decode_or_none
¶
Decode and verify a JWT, returning None on failure.
Convenience wrapper for opportunistic decoding (e.g. soft auth that downgrades the user to anonymous when the token is missing/bad).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token
|
str
|
The token to decode. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any] | None
|
dict[str, Any] | None: The decoded claims, or |
dict[str, Any] | None
|
when the token is invalid or expired. |
Source code in tempest_fastapi_sdk/utils/jwt.py
EmailUtils
¶
EmailUtils(
host: str,
port: int,
*,
from_addr: str,
username: str | None = None,
password: str | None = None,
use_tls: bool = False,
use_starttls: bool = True,
timeout: float = 30.0,
template_dir: str | Path | None = None,
)
Send transactional emails via SMTP.
Connection configuration is supplied at construction time; each
:meth:send call opens a fresh SMTP connection (aiosmtplib's
high-level send helper handles connect/login/quit). For
high-volume scenarios consider holding a persistent connection
via aiosmtplib.SMTP directly.
Attributes:
| Name | Type | Description |
|---|---|---|
host |
str
|
SMTP server hostname. |
port |
int
|
SMTP port. |
from_addr |
str
|
Default sender address used as the |
use_tls |
bool
|
Whether to connect using SSL/TLS from the start (port 465 style). |
use_starttls |
bool
|
Whether to upgrade to TLS via STARTTLS after connect (port 587 style). |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
host
|
str
|
SMTP server hostname. |
required |
port
|
int
|
SMTP port. Common values: |
required |
from_addr
|
str
|
Default sender address. |
required |
username
|
str | None
|
Auth username. |
None
|
password
|
str | None
|
Auth password. |
None
|
use_tls
|
bool
|
Connect using SSL/TLS immediately. Set
this for port |
False
|
use_starttls
|
bool
|
Upgrade to TLS via STARTTLS after
connect. Set this for port |
True
|
timeout
|
float
|
SMTP socket timeout in seconds. |
30.0
|
template_dir
|
str | Path | None
|
Directory holding Jinja2
templates for :meth: |
None
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/email.py
send
async
¶
send(
to: str | Iterable[str],
subject: str,
body: str,
*,
html: str | None = None,
cc: Iterable[str] | None = None,
bcc: Iterable[str] | None = None,
attachments: Iterable[Path] | None = None,
reply_to: str | None = None,
from_addr: str | None = None,
) -> None
Send a single email.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
to
|
str | Iterable[str]
|
Recipient address(es). Listed
in the |
required |
subject
|
str
|
Subject line. |
required |
body
|
str
|
Plain-text body. Always sent; the HTML
alternative is added as a multipart child when
|
required |
html
|
str | None
|
Optional HTML alternative body. |
None
|
cc
|
Iterable[str] | None
|
Additional |
None
|
bcc
|
Iterable[str] | None
|
|
None
|
attachments
|
Iterable[Path] | None
|
Files to attach. |
None
|
reply_to
|
str | None
|
Value for the |
None
|
from_addr
|
str | None
|
Override the default sender for this message. |
None
|
Raises:
| Type | Description |
|---|---|
SMTPException
|
Re-raised on any SMTP error so callers can branch on the specific failure. |
Source code in tempest_fastapi_sdk/utils/email.py
render_template
¶
Render a Jinja2 template from template_dir with context.
The Jinja environment is built lazily on first call and
memoized — subsequent renders reuse the same loader. HTML
autoescaping is enabled for .html / .htm / .xml
templates so caller-supplied values cannot break out into
markup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_name
|
str
|
Template filename relative to
|
required |
context
|
dict[str, Any]
|
Variables exposed inside the template. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Rendered template body — pass this directly to |
str
|
meth: |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
When |
ImportError
|
When Jinja2 is missing (it ships with the
|
TemplateNotFound
|
When the file cannot be located
under |
Example:
>>> emails = EmailUtils(..., template_dir="emails/")
>>> html = emails.render_template(
... "welcome.html",
... {"user_name": "Ana", "app_url": "https://app/"},
... )
>>> await emails.send(
... "ana@example.com",
... subject="Welcome!",
... body="Welcome, Ana!",
... html=html,
... )
Source code in tempest_fastapi_sdk/utils/email.py
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | |
UploadUtils
¶
UploadUtils(
upload_dir: Path | str,
*,
max_size_bytes: int | None = None,
allowed_extensions: set[str] | None = None,
allowed_mimetypes: set[str] | None = None,
verify_magic_bytes: bool = False,
chunk_size: int = 1024 * 1024,
)
Persist uploaded files to local disk with opt-in validation.
Validation is incremental: extension and MIME type are checked
against the configured whitelists before reading any bytes; the
file's real content is optionally sniffed from its first bytes
(verify_magic_bytes); and size is enforced as the stream is
consumed so oversized uploads don't fill the disk before being
rejected.
Saved files are streamed in chunks so memory usage stays bounded regardless of the upload size.
Attributes:
| Name | Type | Description |
|---|---|---|
upload_dir |
Path
|
Base directory where files are persisted. Created on instantiation when missing. |
max_size_bytes |
int | None
|
Reject uploads larger than this.
|
allowed_extensions |
set[str] | None
|
Whitelist of file
extensions (lowercase, no dot). |
allowed_mimetypes |
set[str] | None
|
Whitelist of MIME types
(lowercase). |
verify_magic_bytes |
bool
|
When |
Initialize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
upload_dir
|
Path | str
|
Base directory. Created if missing (parents included). |
required |
max_size_bytes
|
int | None
|
Reject uploads larger than
this. |
None
|
allowed_extensions
|
set[str] | None
|
Whitelist of file
extensions. Leading dots and case are normalized
internally so |
None
|
allowed_mimetypes
|
set[str] | None
|
Whitelist of MIME
types (case-insensitive, e.g. |
None
|
verify_magic_bytes
|
bool
|
Sniff the first bytes of each
upload and reject content that does not match its
declared type / the allow-list. See the class
attribute docs for the caveat. Default |
False
|
chunk_size
|
int
|
Stream read chunk in bytes. Defaults to 1 MiB; raise to trade memory for fewer syscalls. |
1024 * 1024
|
Raises:
| Type | Description |
|---|---|
ImportError
|
When the |
Source code in tempest_fastapi_sdk/utils/upload.py
validate
¶
Validate extension and MIME type before reading the stream.
Size and content (magic bytes) cannot be checked here because
they require reading the stream; they are enforced incrementally
in :meth:save as bytes are consumed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file
|
UploadFile
|
The FastAPI upload to validate. |
required |
Raises:
| Type | Description |
|---|---|
InvalidFileTypeException
|
If the extension or MIME type is not in the configured whitelist. |
Source code in tempest_fastapi_sdk/utils/upload.py
save
async
¶
save(
file: UploadFile,
*,
subdir: str = "",
filename: str | None = None,
keep_original_name: bool = False,
content_validator: Callable[[bytes], bool] | None = None,
storage: UploadStorage | None = None,
) -> Path
Persist file and return the final path.
By default writes to upload_dir on local disk. Pass
storage=MinIOUploadStorage(client) to send the upload to
MinIO/S3 — the validation pipeline (extension / MIME / size /
magic bytes / content_validator) is identical for either
backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file
|
UploadFile
|
The FastAPI upload. |
required |
subdir
|
str
|
Optional sub-directory relative to
|
''
|
filename
|
str | None
|
Explicit final filename (e.g.
|
None
|
keep_original_name
|
bool
|
When |
False
|
content_validator
|
Callable[[bytes], bool] | None
|
Optional
predicate run on the first chunk read from the stream.
Returning |
None
|
storage
|
UploadStorage | None
|
Optional :class: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
Local absolute path when |
Path
|
or |
Raises:
| Type | Description |
|---|---|
InvalidFileTypeException
|
If the extension/MIME violates the
whitelist, the |
FileTooLargeException
|
If the stream exceeds
|
Source code in tempest_fastapi_sdk/utils/upload.py
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 | |
delete
¶
Delete a previously saved file, bounded to upload_dir.
Rejects any path that resolves outside upload_dir — that
way callers can forward a user-supplied filename without
risking rm -rf semantics on the rest of the filesystem.
Absolute paths are accepted only when they land under
upload_dir; everything else is treated as relative to it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
Path | str
|
The file path to delete. Resolved
against |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
|
bool
|
|
Raises:
| Type | Description |
|---|---|
InvalidFileTypeException
|
When |
Source code in tempest_fastapi_sdk/utils/upload.py
MetricsUtils
¶
Aggregated CPU/RAM/disk/GPU readings for the current host.
Built on top of :mod:psutil (always required by the [metrics]
extra) and pynvml (optional — NVIDIA GPU support degrades to
an empty list when the library is missing or no NVIDIA device is
present).
Every method has a synchronous and an asynchronous variant. Sync
methods call :mod:psutil directly (most calls are non-blocking
or block briefly for sampling); async variants run the same code
via :func:asyncio.to_thread so they never stall the event loop
when a longer sampling interval is requested.
Stateless — instantiation is unnecessary; every method is a classmethod.
cpu
classmethod
¶
cpu(*, interval: float = 0.1) -> CPUMetrics
Sample CPU usage.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interval
|
float
|
Sampling window in seconds. |
0.1
|
Returns:
| Name | Type | Description |
|---|---|---|
CPUMetrics |
CPUMetrics
|
The sampled metrics. |
Source code in tempest_fastapi_sdk/utils/metrics.py
cpu_async
async
classmethod
¶
cpu_async(*, interval: float = 0.1) -> CPUMetrics
Asyncio-friendly wrapper around :meth:cpu.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
interval
|
float
|
Sampling window in seconds. |
0.1
|
Returns:
| Name | Type | Description |
|---|---|---|
CPUMetrics |
CPUMetrics
|
The sampled metrics. |
Source code in tempest_fastapi_sdk/utils/metrics.py
memory
classmethod
¶
memory() -> MemoryMetrics
Sample RAM usage.
Returns:
| Name | Type | Description |
|---|---|---|
MemoryMetrics |
MemoryMetrics
|
The current memory snapshot. |
Source code in tempest_fastapi_sdk/utils/metrics.py
memory_async
async
classmethod
¶
memory_async() -> MemoryMetrics
Asyncio-friendly wrapper around :meth:memory.
Returns:
| Name | Type | Description |
|---|---|---|
MemoryMetrics |
MemoryMetrics
|
The current memory snapshot. |
disk
classmethod
¶
disk(path: str = '/') -> DiskMetrics
Sample disk usage for path.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
The filesystem path to inspect. Defaults to the root partition. |
'/'
|
Returns:
| Name | Type | Description |
|---|---|---|
DiskMetrics |
DiskMetrics
|
The usage snapshot. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
When |
Source code in tempest_fastapi_sdk/utils/metrics.py
disks
classmethod
¶
disks(paths: list[str] | None = None) -> list[DiskMetrics]
Sample usage for multiple disks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
list[str] | None
|
Paths to inspect. |
None
|
Returns:
| Type | Description |
|---|---|
list[DiskMetrics]
|
list[DiskMetrics]: One entry per resolvable path; paths |
list[DiskMetrics]
|
that raise are logged and skipped. |
Source code in tempest_fastapi_sdk/utils/metrics.py
disks_async
async
classmethod
¶
disks_async(paths: list[str] | None = None) -> list[DiskMetrics]
Asyncio-friendly wrapper around :meth:disks.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
paths
|
list[str] | None
|
Paths to inspect. |
None
|
Returns:
| Type | Description |
|---|---|
list[DiskMetrics]
|
list[DiskMetrics]: The collected snapshots. |
Source code in tempest_fastapi_sdk/utils/metrics.py
gpus
classmethod
¶
gpus() -> list[GPUMetrics]
Sample NVIDIA GPU usage via pynvml when available.
Returns an empty list (without raising) when:
pynvmlis not installed,- the NVML library cannot be loaded (no NVIDIA driver, WSL without compute, etc.), or
- no NVIDIA devices are present.
Returns:
| Type | Description |
|---|---|
list[GPUMetrics]
|
list[GPUMetrics]: One entry per detected GPU. |
Source code in tempest_fastapi_sdk/utils/metrics.py
gpus_async
async
classmethod
¶
gpus_async() -> list[GPUMetrics]
Asyncio-friendly wrapper around :meth:gpus.
Returns:
| Type | Description |
|---|---|
list[GPUMetrics]
|
list[GPUMetrics]: One entry per detected GPU. |
snapshot
classmethod
¶
snapshot(
*, disk_paths: list[str] | None = None, cpu_interval: float = 0.1
) -> SystemMetrics
Build a full :class:SystemMetrics snapshot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
disk_paths
|
list[str] | None
|
Disks to inspect. |
None
|
cpu_interval
|
float
|
CPU sampling window. |
0.1
|
Returns:
| Name | Type | Description |
|---|---|---|
SystemMetrics |
SystemMetrics
|
The combined snapshot. |
Source code in tempest_fastapi_sdk/utils/metrics.py
snapshot_async
async
classmethod
¶
snapshot_async(
*, disk_paths: list[str] | None = None, cpu_interval: float = 0.1
) -> SystemMetrics
Asyncio-friendly wrapper around :meth:snapshot.
Runs every sub-collector concurrently via :func:asyncio.gather
so the wall-clock cost is bounded by the slowest sample
(typically CPU).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
disk_paths
|
list[str] | None
|
Disks to inspect. |
None
|
cpu_interval
|
float
|
CPU sampling window. |
0.1
|
Returns:
| Name | Type | Description |
|---|---|---|
SystemMetrics |
SystemMetrics
|
The combined snapshot. |
Source code in tempest_fastapi_sdk/utils/metrics.py
LogUtils
¶
LogUtils(
name: str,
*,
level: str | int = "INFO",
json_output: bool = True,
log_dir: str | Path | None = "logs",
stdout: bool = True,
file_output: bool = True,
)
High-level logging facade used across SDK consumers.
Wraps :func:tempest_fastapi_sdk.configure_logging so callers can
obtain a fully configured JSON logger with one line, and exposes
structured info/warning/error/debug/exception
methods that forward **fields as top-level keys on the JSON
payload via Python's logging.LogRecord.extra.
The class can be used in two flavors:
- Instance API — keeps a configured logger as state and exposes level methods directly. Recommended for service-wide singletons.
- Static helpers — :meth:
configureand :meth:get_loggerfor ad-hoc configuration without tying state to an object.
Attributes:
| Name | Type | Description |
|---|---|---|
logger |
Logger
|
The configured stdlib logger. |
name |
str
|
The logger name. |
Configure and bind a logger to this instance.
Mirrors :func:configure_logging defaults — stdout and file
output are enabled out of the box, writing under logs/.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Logger name. Typically |
required |
level
|
str | int
|
Minimum log level to emit. Accepts
stdlib names ( |
'INFO'
|
json_output
|
bool
|
When |
True
|
log_dir
|
str | Path | None
|
Directory for per-level files.
Defaults to |
'logs'
|
stdout
|
bool
|
Attach the stdout handler. Defaults to
|
True
|
file_output
|
bool
|
Attach the per-level + |
True
|
Source code in tempest_fastapi_sdk/utils/log.py
configure
staticmethod
¶
configure(
level: str | int = "INFO",
*,
json_output: bool = True,
logger_name: str | None = None,
log_dir: str | Path | None = "logs",
stdout: bool = True,
file_output: bool = True,
) -> Logger
Imperative shortcut for :func:configure_logging.
Forwards every keyword to :func:configure_logging so the two
share defaults — stdout and file output enabled, logs/
directory used unless overridden.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
str | int
|
Minimum log level. |
'INFO'
|
json_output
|
bool
|
Emit JSON when |
True
|
logger_name
|
str | None
|
Target logger; |
None
|
log_dir
|
str | Path | None
|
Directory for per-level files.
Defaults to |
'logs'
|
stdout
|
bool
|
Attach the stdout handler. Defaults to
|
True
|
file_output
|
bool
|
Attach the per-level + |
True
|
Returns:
| Type | Description |
|---|---|
Logger
|
logging.Logger: The configured logger. |
Source code in tempest_fastapi_sdk/utils/log.py
get_logger
staticmethod
¶
Return the stdlib logger named name without reconfiguring.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The logger name. |
required |
Returns:
| Type | Description |
|---|---|
Logger
|
logging.Logger: The (possibly unconfigured) logger. |
Source code in tempest_fastapi_sdk/utils/log.py
current_request_id
staticmethod
¶
Return the current request ID from the contextvar.
Useful when callers want to surface the correlation ID outside the log line (e.g. in an HTTP response body).
Returns:
| Type | Description |
|---|---|
str | None
|
str | None: The active request ID, or |
Source code in tempest_fastapi_sdk/utils/log.py
info
¶
Emit an INFO record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The log message. |
required |
**fields
|
Any
|
Extra structured fields merged into the JSON payload. |
{}
|
Source code in tempest_fastapi_sdk/utils/log.py
debug
¶
Emit a DEBUG record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The log message. |
required |
**fields
|
Any
|
Extra structured fields. |
{}
|
warning
¶
Emit a WARNING record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The log message. |
required |
**fields
|
Any
|
Extra structured fields. |
{}
|
error
¶
Emit an ERROR record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The log message. |
required |
**fields
|
Any
|
Extra structured fields. |
{}
|
critical
¶
Emit a CRITICAL record.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The log message. |
required |
**fields
|
Any
|
Extra structured fields. |
{}
|
exception
¶
Emit an ERROR record with the current exception traceback.
Must be called from inside an except block — relies on
logger.exception which inspects sys.exc_info().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
str
|
The log message. |
required |
**fields
|
Any
|
Extra structured fields. |
{}
|
Source code in tempest_fastapi_sdk/utils/log.py
AttemptThrottle
¶
AttemptThrottle(
backend: ThrottleBackend,
*,
max_attempts: int,
window_seconds: int,
namespace: str = "throttle",
fail_open: bool = True,
)
Fixed-window failure counter over an injected async KV backend.
Initialize the throttle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backend
|
ThrottleBackend
|
Async KV store (e.g.
|
required |
max_attempts
|
int
|
Failures allowed before a key is
blocked. Must be |
required |
window_seconds
|
int
|
Sliding window length (also the TTL
applied on the first failure). Must be |
required |
namespace
|
str
|
Key prefix so multiple throttles can share a backend without colliding. |
'throttle'
|
fail_open
|
bool
|
When |
True
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in tempest_fastapi_sdk/utils/throttle.py
status
async
¶
status(key: str) -> ThrottleStatus
Read the current status for key without mutating it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The domain key (e.g. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ThrottleStatus |
ThrottleStatus
|
Current attempts / blocked state. On a
backend error with |
Source code in tempest_fastapi_sdk/utils/throttle.py
hit
async
¶
hit(key: str) -> ThrottleStatus
Record one failure for key and return the new status.
Increments the counter and, on the first failure of a window, applies the TTL so the window expires on its own.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The domain key. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
ThrottleStatus |
ThrottleStatus
|
Status after the increment. On a backend
error with |
Source code in tempest_fastapi_sdk/utils/throttle.py
reset
async
¶
Clear the counter for key (e.g. after a success).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The domain key. |
required |
Source code in tempest_fastapi_sdk/utils/throttle.py
raise_if_blocked
async
¶
raise_if_blocked(key: str, *, message: str | None = None) -> ThrottleStatus
Raise :class:TooManyRequestsException when key is blocked.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The domain key. |
required |
message
|
str | None
|
Optional override for the 429 message. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
ThrottleStatus |
ThrottleStatus
|
The (unblocked) status when within budget. |
Raises:
| Type | Description |
|---|---|
TooManyRequestsException
|
When the attempt budget for |