Examples¶
Field types¶
Every field type is exported from pydantic_forms.validators and described in full under
Field types in the reference. They fall into a few groups.
Display-only. Rendered but collect no input, and all are frozen:
Label,
Divider,
Hidden,
callout(),
markdown(),
migration_summary(),
DisplaySubscription.
Text, numbers and dates. Plain str, int, float and bool need no special type and render as text,
number and checkbox inputs. Beyond those:
LongText,
timestamp() /
Timestamp,
Accept,
OrganisationId.
Choices. Choice for one option,
choice_list() for several.
Lists. ListOfOne,
ListOfTwo,
unique_conlist().
Read-only. read_only_field() for a scalar,
read_only_list() for a list.
Contact persons. ContactPerson and
contact_person_list().
A form using them¶
Field types are ordinary annotations, so a page mixes them freely with plain Python types:
from pydantic_forms.core import FormPage
from pydantic_forms.validators import (
Choice,
Divider,
Label,
LongText,
Timestamp,
choice_list,
)
class Speed(Choice):
_1000 = ("1000", "1 Gbit/s")
_10000 = ("10000", "10 Gbit/s")
class Tag(Choice):
core = ("core", "Core")
edge = ("edge", "Edge")
class CreatePortForm(FormPage):
intro: Label = "Configure the new port"
separator: Divider = None
description: LongText
speed: Speed
tags: choice_list(Tag, min_items=1)
starts_at: Timestamp
Submitting it gives back the validated model, with each value coerced to its annotated type:
>>> port = CreatePortForm(
... description="uplink to core",
... speed="1000",
... tags=["core"],
... starts_at=1735689600,
... )
>>> port.speed
<Speed._1000: '1000'>
>>> port.tags
[<Tag.core: 'core'>]
A Choice member is a (value, label) pair, and the generated schema carries the mapping, so the frontend
can show something friendlier than the stored value:
>>> CreatePortForm.model_json_schema()["$defs"]["Speed"]["options"]
{'1000': '1 Gbit/s', '10000': '10 Gbit/s'}
The display-only and text types mostly show up as a format that the frontend switches on, rather than as
extra validation:
>>> CreatePortForm.model_json_schema()["properties"]["description"]["format"]
'long'
>>> CreatePortForm.model_json_schema()["properties"]["intro"]["format"]
'label'
A modify form¶
A modify form usually lets the user change part of what already exists while showing the rest as context.
read_only_field fixes a value and marks it disabled, so the frontend displays it but does not offer it for
editing:
from pydantic_forms.validators import read_only_field
class ModifyPortForm(FormPage):
port_name: read_only_field("uplink-1")
speed: read_only_field("1000")
description: LongText
>>> ModifyPortForm.model_json_schema()["properties"]["port_name"]["extraProperties"]
{'disabled': True, 'value': 'uplink-1'}
The fixed values are normally read from whatever is being modified rather than written as literals, so a form like this is defined inside the generator, where that data is in hand.
Read-only fields may be omitted from the submitted input, and still come back in the result:
>>> ModifyPortForm(description="uplink to core, upgraded").model_dump()
{'port_name': 'uplink-1', 'speed': '1000', 'description': 'uplink to core, upgraded'}
And because each value is fixed, submitting a different one is a validation error rather than a silent overwrite:
>>> ModifyPortForm(port_name="renamed", description="uplink to core")
Traceback (most recent call last):
...
pydantic_core._pydantic_core.ValidationError: ...
More examples¶
For complete form generators in the context of real workflows, see the
example-orchestrator repository. Its
workflows/ directory contains multi-page wizards that branch on earlier input, such as
workflows/l2vpn/create_l2vpn.py.