http.forms

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

Form data parsing and binding — URL-encoded and multipart.

ImplementsMultiValueMappingfor consistent access across Headers, QueryParams, and FormData.

form_from()provides lightweight dataclass binding: define a frozen dataclass, pass…

Form data parsing and binding — URL-encoded and multipart.

ImplementsMultiValueMappingfor consistent access across Headers, QueryParams, and FormData.

form_from()provides lightweight dataclass binding: define a frozen dataclass, pass it toform_from(request, MyForm), and get a populated instance. No magic validation — just binding with type coercion forstr, int, float, bool, datetime.date, datetime.datetime (ISO 8601), decimal.Decimal, uuid.UUID, enum.Enum subclasses, and list[T]for repeated fields such as checkbox groups and multi-selects.

python-multipart is an optional dependency (pip install chirp[forms]). URL-encoded forms use stdliburllib.parse— no extra dependency.

http.forms

Name Type Default Description
type
qualified_name
element_type
description
source_file
line_number
is_autodoc
autodoc_element
_autodoc_template
_autodoc_url_path
_autodoc_page_type
title
doc_content_hash

Symbols on this page

function _sanitize_upload_filename

Reduce an attacker-influenced filename to a safe basename.

Strips directory components (POSIX/ and Windows \), removes NUL bytes, and rejects traversal so…

Jump to symbol
class UploadFile

An uploaded file from a multipart form submission.

Metadata (filename, content_type, size) is immutable— this is a frozen dataclass,…

Jump to symbol
class FormFileFieldError

Raised when a string form accessor is used for an uploaded file field.

FormDatakeeps string fields and uploads in separate maps. Reading a…

Jump to symbol
class FormData

Immutable parsed form data.

ImplementsMapping[str, str] and the MultiValueMappingprotocol. Holds both string field values and uploaded files.

__getitem__ / getreturn string…

Jump to symbol
class FormBindingError

Raised when form data cannot be bound to a dataclass.

Jump to symbol
function _coerce_decimal

Coerce a string toDecimal, normalizing failure to ValueError.

Decimal("nope") raises InvalidOperation (an ArithmeticError), notValueError— so without this the…

Jump to symbol
function _coerce_enum

Coerce a raw form string to anEnummember.

Tries by value first (EnumCls(value)— the common case for forms whose <option value=...>…

Jump to symbol
function _resolve_coercion

Return the coercion callable for a field's resolved target type.

Looks up the fixed_COERCIONS table first, then special-cases Enumsubclasses (each subclass is…

Jump to symbol
async function form_from

Bind form data from a request to a dataclass instance.

Readsrequest.form()and populates the given dataclass. Fields with defaults are optional; fields without…

Jump to symbol
function _extract_field_rules

Collectchirp.validation rules attached via Annotatedmetadata.

A field declared asAnnotated[str, required, max_length(100)]carries its validation rules in the type-hint metadata. This walks…

Jump to symbol
function _annotated_metadata

Return theAnnotated metadata tuple for hint, else None.

The metadata lives directly on theAnnotatedwrapper regardless of the base type's…

Jump to symbol
async function form_or_errors

Bind form data or return a ValidationError for re-rendering.

Combinesform_from() and ValidationErrorinto a single call. On success, returns the populated dataclass. On…

Jump to symbol
function form_values

Extract form field values as strings for template re-population.

Accepts a dataclass instance or aMapping. Returns a flat dict[str, str]suitable for…

Jump to symbol
function _unwrap_optional

Extract the base type fromX | None or plain X.

Jump to symbol
function _list_item_type

Return the item type forlist[T]hints.

Jump to symbol
function _type_name

Best-effort type name for binding errors.

Jump to symbol
async function parse_form_data

Parse form body into FormData.

Supports:

  • application/x-www-form-urlencoded(stdlib, no extra dependency)
  • multipart/form-data (requires python-multipart)
Jump to symbol
function _parse_urlencoded

Parse URL-encoded form data using stdlib.

Jump to symbol
async function _parse_multipart

Parse multipart form data using python-multipart.

File parts are streamed into aSpooledTemporaryFile (spilling to disk pastspool_threshold) rather than buffered whole in…

Jump to symbol
_sanitize_upload_filename
function
def _sanitize_upload_filename(filename: str) -> str

Reduce an attacker-influenced filename to a safe basename.

Strips directory components (POSIX/ and Windows \), removes NUL bytes, and rejects traversal so a multipartfilename="../../etc/passwd" cannot escape a chosen directory. Returns"upload"when nothing usable remains (e.g.".."or all separators).

Parameters

Name Type Default Description
filename str
UploadFile
class

An uploaded file from a multipart form submission.

Metadata (filename, content_type, size) is immutable — this is a frozen dataclass, so rebinding any of those attributes raises dataclasses.FrozenInstanceError. The content is backed by a stdlib SpooledTemporaryFile held in the private _spool field: small files stay in memory, larger ones spill to a temp file on disk pastspool_thresholdbytes — so a multi-GB upload never lands wholly in RAM. (A frozen dataclass forbids rebinding fields, not mutating the object a field points to, so the spool's IO position/buffer remains usable while the metadata stays locked.)

read() returns the full bytes; save()streams to disk in chunks and sanitizes the destination basename against path traversal.

FormFileFieldError
class

Raised when a string form accessor is used for an uploaded file field.

FormDatakeeps string fields and uploads in separate maps. Reading a file-only name throughform[key] / form.get(key)used to look like a missing field (KeyError / None), which silently dropped uploads. This error points callers atform.files[key]instead.

Inherits fromTypeError (wrong accessor kind), not KeyError(absent key), soexcept KeyErrorhandlers do not treat a present upload as missing.

FormData
class

Immutable parsed form data.

ImplementsMapping[str, str] and the MultiValueMappingprotocol. Holds both string field values and uploaded files.

__getitem__ / getreturn string field values only. Uploaded files live underfiles— using a string accessor for a file-only name raises FormFileFieldErrorrather than reporting the field as absent. get_list returns all string values for a key. Membership (in, iteration,len) covers string fields only.

Usage::

form = await request.form()
username = form["username"]
avatar = form.files.get("avatar")  # UploadFile or None
FormBindingError
class

Raised when form data cannot be bound to a dataclass.

_coerce_decimal
function
def _coerce_decimal(value: str) -> Decimal

Coerce a string toDecimal, normalizing failure to ValueError.

Decimal("nope") raises InvalidOperation (an ArithmeticError), notValueError— so without this the existing except ValueError, TypeError path in form_fromwould miss it and the error would escape as an uncaught exception instead of a FormBindingError.

Parameters

Name Type Default Description
value str
_coerce_enum
function
def _coerce_enum(enum_cls: type[Enum], value: str) -> Enum

Coerce a raw form string to anEnummember.

Tries by value first (EnumCls(value)— the common case for forms whose <option value=...>carries the member value), then falls back to by name (EnumCls[value]) which is handy for string enums declared as RED = "red"but submitted by member name. Unknown inputs raise ValueError so form_from reports a FormBindingErrornaming the field — consistent with the int/float path.EnumCls(value)may raise KeyErrorfor some enum shapes, so that is caught too.

Parameters

Name Type Default Description
enum_cls type[Enum]
value str
_resolve_coercion
function
def _resolve_coercion(target_type: Any) -> Callable[[str], Any]

Return the coercion callable for a field's resolved target type.

Looks up the fixed_COERCIONS table first, then special-cases Enum subclasses (each subclass is a distinct type, so it cannot be a static table key). Falls back to calling the type directly (mirrors the prior behavior for unknown types such as plain custom classes).

Parameters

Name Type Default Description
target_type Any
form_from
function async
async def form_from(request: Any, datacls: type[T]) -> T

Bind form data from a request to a dataclass instance.

Readsrequest.form()and populates the given dataclass. Fields with defaults are optional; fields without defaults are required. String fields are stripped of whitespace by default.

Supportsstr, int, float, bool, datetime.dateand datetime.datetime (ISO 8601), decimal.Decimal, uuid.UUID, enum.Enumsubclasses (coerced by member value, then by name), and list[T] type coercion. Missing list fields bind to []because browsers omit unchecked checkbox groups entirely. RaisesFormBindingError with a dict of errors for missing or invalid fields.

Usage::

@dataclass(frozen=True, slots=True)
class TaskForm:
    title: str
    description: str = ""
    priority: str = "medium"

@app.route("/tasks", methods=["POST"])
async def add_task(request: Request):
    form = await form_from(request, TaskForm)
    # form.title, form.description, form.priority are populated

Parameters

Name Type Default Description
request Any A Chirp Request object (anything with an async ``.form()`` method).
datacls type[T] A dataclass class to bind form data into.
_extract_field_rules
function
def _extract_field_rules(datacls: type) -> dict[str, list[Validator]]

Collectchirp.validation rules attached via Annotatedmetadata.

A field declared asAnnotated[str, required, max_length(100)]carries its validation rules in the type-hint metadata. This walks the dataclass fields, resolves hints withinclude_extras=True (so Annotatedis preserved rather than stripped — noteform_fromresolves them without extras, which is why those rules are otherwise never run), and gathers the callable metadata items as validators. Non-callable metadata (doc strings, sentinels, etc.) is ignored, soAnnotatedcan be shared with other tooling.

Optional nesting is unwrapped to find the Annotatedlayer in either order — bothAnnotated[str | None, required](metadata on the outer wrapper) andOptional[Annotated[str, required]]/ Annotated[str, required] | None(metadata on a union member) yield the same rules. Only fields with at least one rule appear in the returned map; a plain dataclass with noAnnotated rules yields {}, so binding behavior is unchanged.

Parameters

Name Type Default Description
datacls type
_annotated_metadata
function
def _annotated_metadata(hint: Any) -> tuple[Any, ...] | None

Return theAnnotated metadata tuple for hint, else None.

The metadata lives directly on theAnnotatedwrapper regardless of the base type's own optionality (Annotated[str | None, required]still exposes__metadata__here).

Parameters

Name Type Default Description
hint Any
form_or_errors
function async
async def form_or_errors(request: Any, datacls: type[T], template_name: str, block_name: str, /, *, retarget: str | None = None, **extra_context: Any) -> T | ValidationError

Bind form data or return a ValidationError for re-rendering.

Combinesform_from() and ValidationErrorinto a single call. On success, returns the populated dataclass. On binding failure, returns aValidationErrorwith the errors and the raw form values for re-population.

Usage::

result = await form_or_errors(request, TaskForm, "tasks.html", "form")
if isinstance(result, ValidationError):
    return result
# result is TaskForm — proceed with validated data

Parameters

Name Type Default Description
request Any A Chirp Request object (anything with an async ``.form()`` method).
datacls type[T] A dataclass class to bind form data into.
template_name str Template name for the error response.
block_name str Block name for the error response.
retarget str | None None Optional ``HX-Retarget`` header value. **extra_context: Additional template context passed to ``ValidationError``.
**extra_context Any
form_values
function
def form_values(form: Any) -> dict[str, str]

Extract form field values as strings for template re-population.

Accepts a dataclass instance or aMapping. Returns a flat dict[str, str] suitable for passing as form=...context toValidationError.

Parameters

Name Type Default Description
form Any A dataclass instance or a ``Mapping``.
_unwrap_optional
function
def _unwrap_optional(hint: Any) -> Any

Extract the base type fromX | None or plain X.

Parameters

Name Type Default Description
hint Any
_list_item_type
function
def _list_item_type(hint: Any) -> type | None

Return the item type forlist[T]hints.

Parameters

Name Type Default Description
hint Any
_type_name
function
def _type_name(value: Any) -> str

Best-effort type name for binding errors.

Parameters

Name Type Default Description
value Any
parse_form_data
function async
async def parse_form_data(body: bytes, content_type: str, *, max_parts: int | None = None, max_total_size: int | None = None, spool_threshold: int | None = None) -> FormData

Parse form body into FormData.

Supports:

  • application/x-www-form-urlencoded(stdlib, no extra dependency)
  • multipart/form-data (requires python-multipart)

Parameters

Name Type Default Description
body bytes Raw request body bytes.
content_type str The Content-Type header value.
max_parts int | None None Optional cap on the number of multipart parts. Exceeding it raises ``PayloadTooLarge`` (413) — the multipart-bomb guard. ``None`` (default) means unbounded for back-compat.
max_total_size int | None None Optional cap on the total accumulated size of multipart parts (the multipart-specific upload ceiling, distinct from the general request-body cap). Exceeding it raises ``PayloadTooLarge`` (413). ``None`` (default) means unbounded for back-compat.
spool_threshold int | None None Bytes a file part keeps in memory before spilling to a temp file on disk. Defaults to ``DEFAULT_SPOOL_THRESHOLD``.
_parse_urlencoded
function
def _parse_urlencoded(body: bytes) -> FormData

Parse URL-encoded form data using stdlib.

Parameters

Name Type Default Description
body bytes
_parse_multipart
function async
async def _parse_multipart(body: bytes, content_type: str, *, max_parts: int | None = None, max_total_size: int | None = None, spool_threshold: int = DEFAULT_SPOOL_THRESHOLD) -> FormData

Parse multipart form data using python-multipart.

File parts are streamed into aSpooledTemporaryFile (spilling to disk pastspool_threshold) rather than buffered whole in a bytearray. max_partscaps the number of parts to defend against a multipart bomb;max_total_sizecaps the cumulative byte size of all parts (the multipart-specific upload ceiling).

RaisesConfigurationError if python-multipartis not installed and PayloadTooLarge if max_parts or max_total_sizeis exceeded.

Parameters

Name Type Default Description
body bytes
content_type str
max_parts int | None None
max_total_size int | None None
spool_threshold int DEFAULT_SPOOL_THRESHOLD

View source · /home/runner/work/chirp/chirp/site/../src/chirp/http/forms.py:1