Forms
Form helpers and types
Form
Server-registered HTML form component. Automatically wires up form submission to a Python handler.
def Form(
*children: Node,
key: str,
onSubmit: EventHandler1[FormData] | None = None,
**props: Unpack[PulseFormProps],
) -> NodeParameters
| Parameter | Type | Default | Description |
|---|---|---|---|
children | Node | - | Form content (inputs, buttons, etc.) |
key | str | required | Unique form identifier |
onSubmit | EventHandler1[FormData] | None | None | Submit handler |
**props | PulseFormProps | - | Standard HTML form props (except action, method, encType, onSubmit) |
Usage
async def handle_submit(data: ps.FormData):
name = data.get("name") # str
file = data.get("avatar") # UploadFile
await save_user(name, file)
def my_form():
return ps.Form(
m.TextInput(name="name", label="Name"),
m.FileInput(name="avatar", label="Avatar"),
m.Button("Submit", type="submit"),
key="user-form",
onSubmit=handle_submit,
)Notes
keymust be a non-empty string unique within the render- Cannot override
action,method,encType, oronSubmitvia props - Uses
multipart/form-dataencoding (supports file uploads) - Handler receives parsed form data as a
FormDatadict
ManualForm
Low-level form handler for custom form implementations.
class ManualForm(Disposable):
def __init__(self, on_submit: EventHandler1[FormData] | None = None)Properties
| Property | Type | Description |
|---|---|---|
is_submitting | bool | Whether form is currently submitting |
registration | FormRegistration | Form registration info (raises if disposed) |
Methods
props() -> GeneratedFormProps
Get form props for manual binding to a form element.
class GeneratedFormProps(TypedDict):
action: str # Form submission URL
method: str # "POST"
encType: str # "multipart/form-data"
onSubmit: Callable[[], None] # Submission triggerupdate(on_submit: EventHandler1[FormData] | None) -> None
Update the submit handler.
dispose() -> None
Unregister the form and clean up.
__call__(*children, key: str | None, **props) -> Node
Render as a form element with children.
Usage
def custom_form():
manual = ManualForm(on_submit=handle_data)
# Option 1: Render directly
return manual(
m.TextInput(name="field"),
m.Button("Submit", type="submit"),
key="my-form",
)
# Option 2: Use props manually
form_props = manual.props()
return m.form(
m.TextInput(name="field"),
m.Button("Submit", type="submit"),
**form_props,
)Types
FormData
Parsed form submission data.
FormData = dict[str, FormValue]Native fields are strings or uploads. Repeated native fields are lists. Structured integrations can also submit nested values.
FormScalar
A non-container form value.
FormScalar = str | int | float | bool | date | datetime | UploadFile | NoneFormValue
A scalar, sequence, mapping, or set submitted by a form integration.
FormValue = (
FormScalar
| Sequence[FormValue]
| Mapping[str, FormValue]
| AbstractSet[FormValue]
)UploadFile
Re-exported from Starlette. Represents an uploaded file.
from starlette.datastructures import UploadFile
class UploadFile:
filename: str | None
size: int | None
content_type: str | None
async def read(self, size: int = -1) -> bytes: ...
async def write(self, data: bytes) -> int: ...
async def seek(self, offset: int) -> int: ...
async def close() -> None: ...Example with Files
async def handle_upload(data: ps.FormData):
file = data.get("document")
if isinstance(file, UploadFile):
content = await file.read()
filename = file.filename or "unnamed"
await save_file(filename, content)Structured form data
Form integrations can submit nested values through Pulse's structured multipart transport. The transport serializes non-file values, sends files as binary multipart parts, and reconstructs the complete value in FormRegistry before calling your handler.
The transport reserves these multipart field names:
__pulse_data__contains the Pulse-serialized value.__pulse_files__contains the file manifest.__pulse_files__.Ncontains the binary file referenced by manifest entryN.
__pulse_data__ and __pulse_files__ are reserved. Using either as a top-level form value raises an error. These fields are an internal transport detail; don't construct them manually.
Nested objects, empty or single-element lists, and uploaded files retain their structure when your onSubmit handler receives them.
Malformed structured payloads return HTTP 400 and don't call the submit handler.
FormRegistry
Internal class managing form registrations. Not typically used directly.
class FormRegistry(Disposable):
def register(
render_id: str,
route_id: str,
session_id: str,
on_submit: Callable[[FormData], Awaitable[None]],
) -> FormRegistration
def unregister(form_id: str) -> None
async def handle_submit(
form_id: str,
request: Request,
session: UserSession,
) -> ResponseFormStorage
Internal hook state for managing form lifecycle within renders. Not typically used directly.
Complete Example
import pulse as ps
from pulse_mantine import components as m
async def create_post(data: ps.FormData):
title = data.get("title", "")
body = data.get("body", "")
image = data.get("image")
if not isinstance(title, str) or not title:
raise ValueError("Title required")
image_url = None
if isinstance(image, ps.UploadFile) and image.filename:
content = await image.read()
image_url = await upload_to_s3(image.filename, content)
await db.posts.create(title=title, body=body, image_url=image_url)
ps.navigate("/posts")
def create_post_form():
return ps.Form(
m.Stack([
m.TextInput(name="title", label="Title", required=True),
m.Textarea(name="body", label="Body", rows=5),
m.FileInput(name="image", label="Image", accept="image/*"),
m.Button("Create Post", type="submit"),
]),
key="create-post",
onSubmit=create_post,
)