# config_schema

URL: https://lbliii.github.io/chirp-ui/api/config_schema/
Section: api
Description: Typed config-schema → form-field projection for chirp-ui.

Deliberately modeled on `chirp_ui.grid_state` and
`chirp_ui.route_tabs`: stdlib + dataclasses only, no ``import chirp`` and
no ``import 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 of `Field`. The same list
the server validates/persists against is projected by `project_fields`()
into `ProjectedField` rows that carry the resolved ``widget``, the
current ``value``, and render-ready ``choices``. The ``config_form`` macro
reads those props directly and never derives them — so persisted server config
and the rendered form cannot drift (the `sort_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") }}

---

> For a complete page index, fetch https://lbliii.github.io/chirp-ui/llms.txt.

Typed config-schema → form-field projection for chirp-ui.

Deliberately modeled on`chirp_ui.grid_state`and
`chirp_ui.route_tabs`: stdlib + dataclasses only, no `import chirp`and
no`import 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 of`Field`. The same list
the server validates/persists against is projected by`project_fields`()
into`ProjectedField` rows that carry the resolved `widget`, the
current`value`, and render-ready `choices`. The `config_form`macro
reads those props directly and never derives them — so persisted server config
and the rendered form cannot drift (the`sort_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

class
Widget

The render target a`ProjectedField`dispatches to.

Each value maps to an existing macro in`forms.html`(text_field,
textarea_field, select_field, toggle_field, range_field, number_scale,
password_field).`StrEnum…`

Jump to symbol

(#Widget)

class
Field

A single config attribute — the developer-authored input.

`name` is the stable form key (matches the server attribute). `type`is the Python type hint…

Jump to symbol

(#Field)

class
ProjectedField

A field projected for rendering. The macro reads these props directly.

`widget` is the resolved `Widget`value (a plain str, so the
template can …

Jump to symbol

(#ProjectedField)

function
project_fields

Project`schema` into render-ready `ProjectedField`rows.

The`sort_columns`() analog for forms. For each field it
resolves the widget (explicit`widget` wins, else `_infer_widget…`

Jump to symbol

(#project_fields)

Widget

class

The render target a`ProjectedField`dispatches to.

Each value maps to an existing macro in`forms.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). `type`is
the Python type hint as a string (`"str"`/`"int"`/`"float"`/`"bool"`)
used for widget inference when`widget` is not set. `choices`are
`(value, label)` pairs (static); `options_callable`supplies them lazily
(e.g. a live model list) and wins over`choices` when set. `secret=True`
forces a password widget and never echoes the value back to the client.
`min`/`max`/`step`drive range/number widgets.

ProjectedField

class

A field projected for rendering. The macro reads these props directly.

`widget` is the resolved `Widget`value (a plain str, so the
template can`{% if pf.widget == "select" %}`). `choices`is a tuple of
`{"value", "label"}` dicts — exactly the shape `select_field`/
`radio_field` / `multi_select_field`already iterate with
`opt.get("value")` / `opt.get("label")`. `value`is masked (empty) for
secrets.

project_fields

function

```
def project_fields(schema: Sequence[Field | Mapping[str, Any]], values: Mapping[str, Any] | None = None) -> list[ProjectedField]
```

Project`schema` into render-ready `ProjectedField`rows.

The`sort_columns`() analog for forms. For each field it
resolves the widget (explicit`widget` wins, else `_infer_widget`()),
reads the current value from`values` (falling back to `default`, masked
to empty for secrets), and normalizes choices to`{"value","label"}`dicts
so the existing`select_field`/`radio_field`macros 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
(https://github.com/lbliii/chirp-ui/blob/main//home/runner/work/chirp-ui/chirp-ui/site/../src/chirp_ui/config_schema.py#L1)
