# How AirModel.to_form() Automatically Generates AirForm Classes in Air

> Discover how AirModel.to_form() automatically generates AirForm classes. Learn how this helper dynamically creates subclasses using Python's type() for efficient form generation.

- Repository: [Feldroy/air](https://github.com/feldroy/air)
- Tags: deep-dive
- Published: 2026-03-01

---

**AirModel.to_form() delegates to the `air.forms.to_form` helper, which dynamically creates a new subclass of AirForm using Python's `type()` constructor, binding the Pydantic model and optional rendering parameters before returning an instantiated form object.**

The `feldroy/air` repository streamlines web development by bridging Pydantic models and HTML forms through automatic class generation. Understanding how **AirModel.to_form()** constructs matching **AirForm** classes reveals the framework's zero-configuration approach to form handling. This mechanism eliminates repetitive boilerplate while maintaining full validation and rendering capabilities.

## The Two-Step Generation Mechanism

The conversion process operates through a thin delegation layer followed by runtime subclass construction.

### Step 1: Invocation from the Model Class

In [`src/air/models.py`](https://github.com/feldroy/air/blob/main/src/air/models.py), the `AirModel` base class defines the `to_form()` classmethod (lines 22-30). This method acts as a forwarder, passing the model class (`cls`) along with optional arguments—`name`, `includes`, and `widget`—directly to the `air.forms.to_form` helper function.

### Step 2: Dynamic Subclass Creation with type()

The core implementation resides in [`src/air/forms.py`](https://github.com/feldroy/air/blob/main/src/air/forms.py) within the `to_form` function. This utility builds a **new subclass of AirForm** on the fly using Python's built-in `type()` constructor. It configures the class by setting `model` to the supplied Pydantic model, storing optional `includes` for field filtering, and attaching a custom `widget` callable if provided. The function generates a class name—either the user-supplied `name` or the default `"{ModelName}Form"`—attaches a docstring, and **instantiates the class** before returning the object.

## How Generated AirForm Classes Function

Generated classes inherit all standard behavior from `air.forms.AirForm`. The `model` attribute maintains a reference to the original **AirModel** (a Pydantic `BaseModel`), ensuring type definitions remain synchronized. When you invoke `validate()` on the form, the underlying Pydantic model executes the actual data validation against field constraints. Rendering logic delegates to `default_form_widget` (or your custom widget), which inspects the model's fields, constraints, and any validation errors to produce HTML output.

## Practical Implementation Example

```python
import air

class ContactModel(air.AirModel):
    name: str
    email: str
    message: str | None = None

# Generate a form class with selective fields and custom widget

ContactForm = ContactModel.to_form(
    includes=["name", "email"],          # render only these fields

    widget=lambda **kw: air.Div(...),    # optional custom renderer

)

# Use the generated form in a route handler

@app.page
def contact_page() -> air.Html:
    form = ContactForm()                 # instance of the dynamic class

    return air.layouts.mvpcss(
        air.H1("Contact us"),
        air.Form(form.render(), air.Button("Send"))
    )

```

In this implementation, `ContactModel.to_form()` triggers the dynamic creation of `ContactModelForm` (or the supplied name) that inherits from `AirForm`. The returned `ContactForm` object functions as a fully instantiated form with bound validation and rendering capabilities.

## Key Source Files and Their Roles

- **[`src/air/models.py`](https://github.com/feldroy/air/blob/main/src/air/models.py)**: Defines the `AirModel` base class and the `to_form` classmethod that initiates the generation request.
- **[`src/air/forms.py`](https://github.com/feldroy/air/blob/main/src/air/forms.py)**: Contains the `to_form` helper function that constructs dynamic subclasses using `type()`, and defines the `AirForm` base class handling validation and rendering.
- **[`src/air/forms.py`](https://github.com/feldroy/air/blob/main/src/air/forms.py)** (also): Houses `default_form_widget`, the default HTML renderer used by generated forms when no custom widget is specified.

## Summary

- **AirModel.to_form()** provides a zero-boilerplate convenience wrapper for form generation.
- The **`air.forms.to_form`** helper uses Python's `type()` to construct new **AirForm** subclasses at runtime.
- Generated forms bind the original Pydantic model via the `model` attribute and delegate validation to Pydantic's logic.
- Optional parameters enable field filtering (`includes`) and custom HTML rendering (`widget`).
- The architecture maintains synchronization between model definitions and form behavior without manual class maintenance.

## Frequently Asked Questions

### What parameters can I pass to AirModel.to_form()?

The method accepts three optional parameters: `name` (specifies the generated class name), `includes` (list of field names to render), and `widget` (a callable for custom HTML generation). These pass through to `air.forms.to_form` to configure the dynamic subclass.

### How does the generated form validate submitted data?

The generated **AirForm** subclass delegates validation to its bound Pydantic model. When you call `validate()` on the form instance, it uses the underlying **AirModel** (a Pydantic `BaseModel`) to enforce the original field constraints, types, and validation rules defined in the model class.

### Can I customize HTML rendering for dynamically generated forms?

Yes. Pass a callable to the `widget` parameter when calling `to_form()`. This replaces the default `default_form_widget` function, allowing you to control HTML output while the framework automatically handles field structure and error display based on the Pydantic model schema.

### Where is the dynamic class creation logic implemented?

According to the `feldroy/air` source code, the dynamic subclass creation occurs in **[`src/air/forms.py`](https://github.com/feldroy/air/blob/main/src/air/forms.py)** within the `to_form` function, while the entry point that model classes use resides in **[`src/air/models.py`](https://github.com/feldroy/air/blob/main/src/air/models.py)** via the `AirModel.to_form()` classmethod.