Typed config-schema → form-field projection for chirp-ui.
Deliberately modeled onchirp_ui.grid_stateand
chirp_ui.route_tabs: stdlib + dataclasses only, no import chirpand
noimport kida. Fully unit-testable with plain pytest and ty-checkable
without a render ("works without Chirp, better with Chirp").
A developer declares a config once as a list ofField. The same list
the server validates/persists against is projected byproject_fields()
intoProjectedField rows that carry the resolved widget, the
currentvalue, and render-ready choices. The config_formmacro
reads those props directly and never derives them — so persisted server config
and the rendered form cannot drift (thesort_columns() analog
for forms).
Example (Chirp route)::
from chirp_ui import Field, Widget, project_fields
MODEL_SETTINGS = [
Field("model", type="str", label="Model", default="gpt-4o",
choices=(("gpt-4o", "GPT-4o"), ("claude", "Claude"))),
Field("temperature", type="float", label="Temperature",
default=0.7, min=0.0, max=2.0, step=0.1),
Field("stream", type="bool", label="Stream responses", default=True),
Field("api_key", type="str", label="API key", secret=True),
Field("system_prompt", type="str", label="System prompt",
widget=Widget.TEXTAREA, default=""),
]
fields = project_fields(MODEL_SETTINGS, values=load_settings(user))
# -> template: {{ config_form(fields, action="/settings", method="post") }}
config_schema
| 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
The render target aProjectedFielddispatches to.
Each value maps to an existing macro informs.html(text_field,
textarea_field, select_field, toggle_field, range_field, number_scale,
password_field).StrEnum…
A single config attribute — the developer-authored input.
name is the stable form key (matches the server attribute). typeis the Python type hint…
A field projected for rendering. The macro reads these props directly.
widget is the resolved Widgetvalue (a plain str, so the
template can …
Projectschema into render-ready ProjectedFieldrows.
Thesort_columns() analog for forms. For each field it
resolves the widget (explicitwidget wins, else _infer_widget…
Widget
class
The render target aProjectedFielddispatches to.
Each value maps to an existing macro informs.html(text_field,
textarea_field, select_field, toggle_field, range_field, number_scale,
password_field).StrEnum so widget == "select"works in templates.
Field
class
A single config attribute — the developer-authored input.
name is the stable form key (matches the server attribute). typeis
the Python type hint as a string ("str"/"int"/"float"/"bool")
used for widget inference whenwidget is not set. choicesare
(value, label) pairs (static); options_callablesupplies them lazily
(e.g. a live model list) and wins overchoices when set. secret=True
forces a password widget and never echoes the value back to the client.
min/max/stepdrive range/number widgets.
ProjectedField
class
A field projected for rendering. The macro reads these props directly.
widget is the resolved Widgetvalue (a plain str, so the
template can{% if pf.widget == "select" %}). choicesis a tuple of
{"value", "label"} dicts — exactly the shape select_field/
radio_field / multi_select_fieldalready iterate with
opt.get("value") / opt.get("label"). valueis masked (empty) for
secrets.
project_fields
function
def project_fields(schema: Sequence[Field | Mapping[str, Any]], values: Mapping[str, Any] | None = None) -> list[ProjectedField]
Projectschema into render-ready ProjectedFieldrows.
Thesort_columns() analog for forms. For each field it
resolves the widget (explicitwidget wins, else _infer_widget()),
reads the current value fromvalues (falling back to default, masked
to empty for secrets), and normalizes choices to{"value","label"}dicts
so the existingselect_field/radio_fieldmacros consume them
unchanged. The macro never recomputes any of this — the server schema and
the rendered control are the same source of truth.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
schema
|
Sequence[Field | Mapping[str, Any]]
|
— | |
values
|
Mapping[str, Any] | None
|
None
|
View source · /home/runner/work/chirp-ui/chirp-ui/site/../src/chirp_ui/config_schema.py:1