Skip to main content

Pydantic-native form validation and rendering. Define a model, get a validated, rendered HTML form. Works with or without Air web framework.

Project description

AirForm

PyPI version

Pydantic-native form validation and rendering for Air. Define an AirModel, get a validated, rendered HTML form with CSRF protection.

Features

  • Type-safe validated data via AirForm[MyModel] generic parameter
  • Works with AirModel (database-backed forms) and plain BaseModel (contact forms, search, etc.)
  • Reads the full AirField metadata vocabulary: Widget, Label, Placeholder, HelpText, Choices, Autofocus, PrimaryKey, Hidden, ReadOnly
  • Auto-skips PrimaryKey and Hidden("form") fields in rendered output
  • HTML5 validation attributes from Pydantic constraints (minlength, maxlength, required)
  • Accessible by default: aria-invalid, aria-describedby, role="alert" on errors
  • Textarea, select, and checkbox rendering from type annotations and metadata
  • Zero-config CSRF protection: render() embeds a signed token, validate() checks it
  • Swappable widget for custom renderers
  • from_request() for async ASGI request handling (works with FastAPI Depends)

Quick start

Database-backed form (most common)

from airmodel import AirModel, AirField
from airform import AirForm
import air

app = air.Air()

class BookOrder(AirModel):
    id: int | None = AirField(default=None, primary_key=True)
    title: str = AirField(label="Book Title", min_length=1)
    quantity: int = AirField(label="Quantity")

class BookOrderForm(AirForm[BookOrder]):
    pass

@app.page
def order_page(request: air.Request):
    return air.Html(
        air.H1("Order a Book"),
        air.Form(
            air.Raw(BookOrderForm().render()),
            air.Button("Order", type_="submit"),
            method="post", action="/order",
        ),
    )

@app.post("/order")
async def submit_order(request: air.Request):
    form = await BookOrderForm.from_request(request)
    if form.is_valid:
        await BookOrder.create(
            title=form.data.title,
            quantity=form.data.quantity,
        )
        return air.Html(air.H1(f"Ordered: {form.data.title}"))
    return air.Html(
        air.Form(
            air.Raw(form.render()),
            air.Button("Order", type_="submit"),
            method="post", action="/order",
        ),
    )

Plain form (no database)

from pydantic import BaseModel
from airfield import AirField
from airform import AirForm

class ContactMessage(BaseModel):
    name: str = AirField(label="Name", autofocus=True)
    email: str = AirField(type="email", label="Email")
    message: str = AirField(widget="textarea", label="Message")

class ContactForm(AirForm[ContactMessage]):
    pass

form = ContactForm()
form.validate({"name": "Audrey", "email": "audreyfeldroy@example.com", "message": "Hello!"})
if form.is_valid:
    send_email(form.data.name, form.data.email, form.data.message)

html = ContactForm().render()

Documentation

Documentation is built with Zensical and deployed to GitHub Pages.

API documentation is auto-generated from docstrings using mkdocstrings.

Docs deploy automatically on push to main via GitHub Actions. To enable this, go to your repo's Settings > Pages and set the source to GitHub Actions.

Installation

uv add AirForm

CLI

Preview rendered form HTML from any Pydantic model:

airform preview myapp.models:ContactModel

Development

See CONTRIBUTING.md for setup instructions.

Author

AirForm was created in 2026 by Audrey M. Roy Greenfeld, extending Daniel Roy Greenfeld's original form rendering design from Air.

Built with Cookiecutter and the audreyfeldroy/cookiecutter-pypackage project template.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

airform-0.4.1.tar.gz (76.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

airform-0.4.1-py3-none-any.whl (12.8 kB view details)

Uploaded Python 3

File details

Details for the file airform-0.4.1.tar.gz.

File metadata

  • Download URL: airform-0.4.1.tar.gz
  • Upload date:
  • Size: 76.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for airform-0.4.1.tar.gz
Algorithm Hash digest
SHA256 2333abcea93773e387f98e02b9d0f2f96f06280ea3da36421e3b04d203ab00b2
MD5 10109dfb02eb56d7ac8cc52090f08f63
BLAKE2b-256 fe2ec7b53b61ce01ef22a00df2eba8dee37d23d764e1288fe73f387eeffb53ba

See more details on using hashes here.

Provenance

The following attestation bundles were made for airform-0.4.1.tar.gz:

Publisher: publish.yml on feldroy/AirForm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file airform-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: airform-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 12.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for airform-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 36102a101ac75fe2ba98f92d5589642c0c5d6ae5670c56ec96d0b0718b3f7d3b
MD5 bcb15e396534bbf479bf2178f06e9536
BLAKE2b-256 c4ed60c4408eed62433678f0e525e335b23ea14e8db49cb21a9c7a3b4098d3b3

See more details on using hashes here.

Provenance

The following attestation bundles were made for airform-0.4.1-py3-none-any.whl:

Publisher: publish.yml on feldroy/AirForm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page