Skip to content

Responses

The base response and every response class shipped with the framework.

Response

Base HTTP response.

Usage::

from veloce import Response

async def handler(request):
    return Response(body=b"hello", content_type="text/plain")

body property writable

body: bytes

Return the response body bytes.

media_type property writable

media_type: str

Return the full Content-Type including parameters.

is_json property

is_json: bool

True when Content-Type is JSON.

Matches application/json and any application/*+json structured suffix (RFC 6839 Sec. 3.1).

mimetype property writable

mimetype: str

The bare media type - Content-Type without parameters.

text/html; charset=utf-8 -> text/html. Lower-cased and stripped per RFC 9110 Sec. 8.3 (media types are case-insensitive).

status property writable

status: str

Full HTTP status line, e.g. "200 OK".

Assignable: accepts an int (200), a bare numeric string ("200"), or a full status line ("200 OK" / "404 Not Found"). The leading integer is parsed into status_code.

content_length property

content_length: int

Length of the response body in bytes.

Always derived from len(body). Streaming responses (which don't materialise the body) return 0 here; see is_streamed.

is_streamed property

is_streamed: bool

True when the response body is a streaming iterator.

charset property writable

charset: str

Response charset from Content-Type.

Falls back to "utf-8" when no charset parameter is present. Assignable: setting it rewrites the charset= parameter on the existing Content-Type (the bare media type is preserved).

mimetype_params property

mimetype_params: dict[str, str]

Parameters of the Content-Type header.

Everything after the bare media type, as a dict of lower-cased parameter names to their (unquoted) values. For text/html; charset=utf-8 this is {"charset": "utf-8"}. Returns an empty dict when no parameters are present.

last_modified property writable

last_modified: Any

Parsed Last-Modified header -> UTC datetime or None.

Accepts the three RFC 9110 Sec. 5.6.7 HTTP-date forms. Returns None on missing/unparseable.

expires property writable

expires: Any

Parsed Expires header -> UTC datetime or None (RFC 9111 Sec. 5.3).

cookies property

cookies: dict[str, str]

Parsed cookie jar from this response's Set-Cookie header(s).

Walks every Set-Cookie entry (Q44 separator \r\nSet-Cookie: respected) and returns {name: value}. Values are percent-decoded, so a cookie reads back as the string set_cookie() was given rather than its wire form. Multiple cookies with the same name resolve to the last set - matches the wire behaviour where the client also keeps the most-recent value. Caller introspection only; mutation goes through set_cookie().

headerlist property

headerlist: list[tuple[str, str]]

Headers flattened to the (name, value) tuple list the wire emit sends.

Each Set-Cookie (Q44 multi-cookie join) expands to its own tuple, so the caller gets the per-cookie view ASGI requires. Two spellings of one field name collapse to a single entry carrying the last name and value seen, at the position of the first (RFC 9110 Sec. 5.1 makes field names case-insensitive), and a CR/LF/NUL anywhere in a name or value raises ValueError rather than being handed back - both matching the emit paths. Response.headers remains the raw, unfolded view.

data property writable

data: bytes

Body bytes alias for Response.body.

Read returns the current body; writing through the setter replaces the body, invalidates any cached HTTP/1.1 encoded bytes (_encoded), and updates Content-Length on the headers if it was previously set.

vary property writable

vary: Any

The Vary header as a HeaderSet.

Returns a fresh HeaderSet parsed from the current header. Assign a HeaderSet, iterable of strings, or a comma-separated string to replace it. Mutating the returned object does not write back - call add_vary(...) or reassign for that.

allow property writable

allow: Any

The Allow header as a HeaderSet.

Lists the HTTP methods the resource supports (RFC 9110 Sec. 10.2.1). Assign a HeaderSet, iterable, or comma-separated string.

www_authenticate property writable

www_authenticate: str | None

The WWW-Authenticate challenge header - RFC 9110 Sec. 11.6.1.

Sent on 401 Unauthorized to tell the client which auth scheme(s) to use. None when unset.

content_encoding property writable

content_encoding: str | None

The Content-Encoding header - RFC 9110 Sec. 8.4. None when unset.

content_language property writable

content_language: str | None

The Content-Language header - RFC 9110 Sec. 8.5. None when unset.

accept_ranges property writable

accept_ranges: str | None

The Accept-Ranges header - RFC 9110 Sec. 14.3.

Typically bytes (range requests supported) or none (explicitly unsupported). None when the header is unset.

content_range property

content_range: str | None

The raw Content-Range header - RFC 9110 Sec. 14.4. None if unset.

date property writable

date: Any

The Date header as a tz-aware UTC datetime - RFC 9110 Sec. 6.6.1.

Returns None when unset or unparseable. Assign a datetime or POSIX timestamp to set it; assign None to remove it.

location property writable

location: str | None

The Location header - RFC 9110 Sec. 10.2.2. None when unset.

content_location property writable

content_location: str | None

The Content-Location header - RFC 9110 Sec. 8.7. None when unset.

retry_after property writable

retry_after: Any

The Retry-After header - RFC 9110 Sec. 10.2.3.

Returns an int (delay in seconds) when the header is numeric, a tz-aware datetime when it's an HTTP-date, or None when unset. Assign an int / timedelta / datetime to set it; assign None to remove it.

age property writable

age: int | None

The Age header in seconds - RFC 9110 Sec. 5.1. None when unset.

cache_control property

cache_control: Any

Parsed Cache-Control header (read-only view).

For setting directives, prefer set_cache_control(...) which writes the header directly. This property is convenient for introspection: resp.cache_control.max_age, resp.cache_control.no_store, etc.

get_json

get_json() -> Any

Parse the response body as JSON.

Returns None for an empty body. Useful in tests to inspect a JSON response without re-decoding body by hand. Raises if the body is non-empty and not valid JSON.

encode

encode() -> bytes

Encode to raw HTTP/1.1 bytes - called once, cached.

set_cookie(key: str, value: str, max_age: Any = None, expires: Any = None, path: str = '/', domain: str | None = None, secure: bool = False, httponly: bool = False, samesite: str | None = 'Lax', partitioned: bool = False, prefix: Literal['host', 'secure'] | None = None) -> None

Build a Set-Cookie header per RFC 6265.

The cookie name must be a valid RFC 6265 token (no spaces, separators, or control characters) and must not collide with a cookie-attribute keyword (Path, Max-Age, ...); a violation raises ValueError.

samesite defaults to "Lax" - a CSRF-resistant default that matches modern browser behaviour. Pass samesite="None" (with secure=True) for a cookie that must travel on cross-site requests, or samesite=None/"" to omit the attribute.

expires= accepts a datetime, a Unix timestamp int|float, or an already-formatted IMF-fixdate str. When both max_age and expires are set, both are emitted (RFC 6265 Sec. 5.2.2: clients prefer Max-Age when supported, falling back to Expires on legacy IE).

partitioned=True adds the CHIPS Partitioned attribute (Cookies Having Independent Partitioned State) - a partitioned cookie is keyed to the top-level site, so embedded third-party contexts each get an isolated jar. Partitioned requires Secure, so it is only emitted when secure=True.

prefix="host" / prefix="secure" add the RFC 6265bis Sec. 4.1.3 name prefix (__Host- / __Secure-) and enforce its invariants: "secure" requires secure=True; "host" also requires path="/" and no domain. A violation raises ValueError.

The cookie name and value are rejected if they contain CR, LF, or NUL - untrusted data must not be able to inject additional cookies or response headers. dump_cookie performs that CRLF check on all five fields (name, value, domain, path, samesite), so set_cookie does not repeat it.

calculate_content_length

calculate_content_length() -> int

Set Content-Length from len(body) and return the value.

Useful when a caller mutates body directly and wants the header to follow. The ASGI emit path computes Content-Length from body on the fly anyway; this helper is for callers that want it locked into self.headers ahead of time.

set_data

set_data(value: bytes | str) -> None

Replace the response body.

Accepts bytes or str (UTF-8 encoded). Invalidates the cached HTTP/1.1 encode so the new body wire-out on the next emit. Refreshes Content-Length when previously set on the headers.

set_cache_control

set_cache_control(max_age: int | None = None, public: bool = False, private: bool = False, no_cache: bool = False, no_store: bool = False, must_revalidate: bool = False, immutable: bool = False, s_maxage: int | None = None) -> str

Build and set the Cache-Control header - RFC 9111 Sec. 5.2.

Combines the standard directives in the order RFC 9111 Sec. 5.2 documents. Values that are False / None are omitted, so a plain resp.set_cache_control(max_age=3600, public=True) produces Cache-Control: public, max-age=3600. Returns the value set.

add_vary

add_vary(*header_names: str) -> str

Append header names to the Vary response header - RFC 9110 Sec. 12.5.5.

Merges with any existing Vary value (de-duplicates, case-insensitive). Returns the resulting header value. Useful when middleware wants to communicate "this response depends on the named request headers" without clobbering existing entries.

set_basic_auth_challenge

set_basic_auth_challenge(realm: str = 'Authentication Required') -> str

Write a Basic WWW-Authenticate challenge - RFC 7617.

Convenience for the common 401 case: WWW-Authenticate: Basic realm="<realm>", charset="UTF-8". Returns the header value written.

set_content_range

set_content_range(start: int | None, stop: int | None, length: int | None, unit: str = HEADER_VALUE_BYTES) -> str

Write a Content-Range header - RFC 9110 Sec. 14.4.

  • set_content_range(0, 499, 1234) -> bytes 0-499/1234.
  • start/stop both None -> an unsatisfied-range response: bytes */1234 (length required in that form).
  • length None -> unknown total: bytes 0-499/*.

Returns the header value written.

set_etag

set_etag(etag: str, weak: bool = False) -> None

Set the ETag header from an explicit value.

Quotes the value if the caller passed it bare. Prepends W/ when weak=True. Use add_etag() for body-derived MD5 ETags; set_etag is for callers that already have an authoritative tag (DB revision, commit hash, version counter).

get_etag

get_etag() -> tuple[str | None, bool]

Return (etag, is_weak) parsed from the ETag header.

(None, False) when unset. Returned tag keeps its quotes so it compares directly with If-None-Match values.

freeze

freeze() -> None

Pre-compute the cached HTTP/1.1 encode.

For buffered responses, populates _encoded so subsequent access pays no encode cost. For streaming responses, no-op. Used by response caching layers that want immutable bytes.

iter_encoded

iter_encoded() -> Any

Yield the response body.

Return type is mode-dependent and the two modes are NOT interchangeable:

  • Buffered response (is_streamed is False) -> returns a synchronous iterator yielding bytes. Drain with for.
  • Streaming response (is_streamed is True) -> returns the underlying async iterator (AsyncIterator[bytes]). Drain with async for.

Callers must branch on response.is_streamed (or use inspect.isasyncgen / hasattr(it, "__aiter__")) to pick the right loop, e.g.:

it = response.iter_encoded()
if response.is_streamed:
    async for chunk in it:
        ...
else:
    for chunk in it:
        ...

The return shape is mode-dependent: a buffered response yields a synchronous iterator of bytes, a streaming response yields the underlying AsyncIterator[bytes]. Branch on response.is_streamed to drain with the right loop.

iter_chunked

iter_chunked(size: int) -> Any

Yield the response body in fixed-size chunks.

Return type is mode-dependent and the two modes are NOT interchangeable:

  • Buffered response (is_streamed is False) -> returns a synchronous generator yielding bytes slices of length size (the final slice may be shorter). Drain with for.
  • Streaming response (is_streamed is True) -> returns the underlying async iterator unchanged (AsyncIterator[bytes]); size is ignored because chunk boundaries are controlled by the source generator, not the caller. Drain with async for.

Pick the loop based on response.is_streamed:

it = response.iter_chunked(4096)
if response.is_streamed:
    async for chunk in it:
        ...
else:
    for chunk in it:
        ...

size must be positive. The return shape is mode-dependent: branch on response.is_streamed to drain with the right loop.

add_etag

add_etag(weak: bool = False) -> str

Compute and attach an ETag derived from the body.

Uses MD5 of the response body, opaque-quoted per RFC 9110 Sec. 8.8.3. weak=True prepends W/ so the validator is treated as a weak match (matching content but possibly different byte-for-byte). Sets ETag even if one was already set; pass the explicit ETag in __init__(headers=...) to skip this. Returns the value set.

make_conditional

make_conditional(request: Any) -> Response

Downgrade this response to 304 when the request's preconditions match the response's ETag / Last-Modified.

Checks If-None-Match first (per RFC 9110 Sec. 13.2 precedence), then If-Modified-Since. On a match, mutates self to status 304 with no body. Returns self so callers can use it inline: return resp.make_conditional(request).

Handles If-None-Match: * (matches any current representation of the resource) and the weak/strong ETag comparison rules.

check_preconditions

check_preconditions(request: Any) -> Response

Enforce the write-side If-Match precondition (RFC 9110 Sec. 13.1.1).

Raises PreconditionFailed (412) when the request carries an If-Match header that the response's current ETag does not satisfy under the strong comparison (Sec. 8.8.3.1) - the lost-update guard. If-Match: * is satisfied whenever a current representation exists, approximated here by the presence of an ETag header. With no If-Match header the response is returned unchanged. Returns self so it can be chained: return resp.check_preconditions(request).

Invoke this inside a handler (where HTTPException is converted to a response); it raises rather than mutating the status.

set_content_disposition

set_content_disposition(disposition: str = HEADER_VALUE_ATTACHMENT, filename: str | None = None) -> str

Write a Content-Disposition header - RFC 6266.

disposition is "attachment" (force download) or "inline" (render in-browser). When filename is given, an ASCII quotable name uses filename="..." (spaces and punctuation preserved, only \ and " escaped); a non-ASCII or non-quotable name uses only the RFC 5987 filename*=UTF-8''... form, with no lossy legacy slot. Returns the header value written.

delete_cookie(key: str, path: str = '/', domain: str | None = None, secure: bool = False, httponly: bool = False, samesite: str | None = None, partitioned: bool = False, prefix: Literal['host', 'secure'] | None = None) -> None

Delete a cookie by overwriting it with an empty value + Max-Age=0.

The browser only treats the new cookie as a replacement for the existing one if Path, Domain, and the Secure / SameSite / Partitioned attributes match - otherwise it stores both. So a session cookie originally set with Secure; SameSite=None (or with Partitioned) will not be deleted by a plain delete_cookie(key) call. Pass the same flags here. prefix deletes the cookie under its true __Host-/__Secure- wire name and enforces the same invariants on the deletion's attributes.

JSONResponse

Bases: Response

JSON response using orjson for speed.

Usage::

from veloce import JSONResponse

async def handler(request):
    return JSONResponse({"ok": True}, status_code=200)

from_bytes classmethod

from_bytes(body: bytes, *, status_code: int = HTTP_200_OK, headers: dict[str, str] | None = None) -> JSONResponse

Build a JSONResponse from already-encoded JSON bytes.

Skips __init__'s orjson re-encode - use this when the caller has produced the JSON body itself (e.g. with custom orjson options or via a JSONProvider.dumps). The body is sent verbatim with Content-Type taken from cls.default_media_type (so a subclass like class ProblemJSON(JSONResponse): default_media_type = "application/problem+json" gets its declared type without overriding this method).

The caller is responsible for ensuring body is valid UTF-8 JSON; no parsing or validation is performed. Passing non-JSON bytes will produce a response whose body does not match its declared content type.

body must be bytes or bytearray. A str raises TypeError rather than being silently encoded, so callers do not produce a response with a mismatched charset by accident.

Header precedence: when headers includes a Content-Type entry, the caller-supplied value wins and the class default is not emitted. This matches Response's general rule that user headers override framework defaults and lets callers send application/problem+json or another JSON suffix type without subclassing.

ORJSONResponse

Bases: JSONResponse

Explicit orjson-backed JSON response.

JSONResponse already uses orjson for encoding, so this class is a semantic alias - useful when route declarations want to communicate the encoder choice via response_class=ORJSONResponse.

UJSONResponse

Bases: Response

JSON response encoded with ujson.

Lazily imports ujson at construction. Raises ImportError with a clear message when the package is missing rather than at module load, so apps that don't use this class don't need ujson installed.

HTMLResponse

Bases: _TextResponse

HTML response.

PlainTextResponse

Bases: _TextResponse

Plain text response.

RedirectResponse

Bases: Response

HTTP redirect.

StreamingResponse

Bases: Response

Streaming response for large payloads.

content may be an async iterator/iterable or a plain sync iterable (e.g. a generator). A sync iterable is wrapped so the response always exposes an async stream; both forms are accepted.

Usage::

from veloce import StreamingResponse

async def handler(request):
    def chunks():
        yield b"part-1"
        yield b"part-2"

    return StreamingResponse(chunks(), content_type="text/plain")

encode

encode() -> bytes

For streaming, encode headers with chunked transfer.

stream_to async

stream_to(transport: Any, drain: Callable[[], Awaitable[None]] | None = None) -> None

Stream chunks to transport.

When drain is supplied (the raw serving protocol passes its write-side flow-control awaitable) it is awaited after each chunk, so a producer outrunning a slow client is throttled instead of growing the transport write buffer without bound. drain is a no-op until the buffer crosses the high-water mark, so the fast path pays one already-set check.

FileResponse

Bases: Response

Serve a file from disk - small files inline, large files via executor.

from_path async classmethod

from_path(path: str, filename: str | None = None, content_type: str | None = None, headers: dict[str, str] | None = None, content_disposition_type: str = HEADER_VALUE_ATTACHMENT) -> FileResponse

Async factory - reads small files inline, large files in the executor.

Stats the path on the loop (one fast syscall) to size the file. A file at or below _INLINE_READ_MAX is read inline, skipping the thread-pool hop that otherwise dominates serving a small static asset; a larger file is read in the executor so a big read never stalls the loop.