Select form does not show PydanticCustomError on page
vcvandersluis opened this issue · comments
vcvandersluis commented
In the forms demo (https://fastui-demo.onrender.com/forms/big).
The name input should start with a capital; otherwise, it will display the PydanticCustomError in red.
I want to implement this PydanticCustomError behavior for the multi-select component, where selecting both A and B together is not allowed.
My code:
# %% command
# uvicorn forms_standalone:router --reload --reload-dir . --port 8003
# %% imports
from __future__ import annotations as _annotations
import enum
from contextlib import asynccontextmanager
from typing import Annotated, ClassVar, List, Literal, TypeAlias, Union
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, PlainTextResponse
from fastui import AnyComponent, FastUI
from fastui import components as c
from fastui import prebuilt_html
from fastui.auth import fastapi_auth_exception_handling
from fastui.dev import dev_fastapi_app
from fastui.events import AuthEvent, GoToEvent, PageEvent
from fastui.forms import FormFile, SelectSearchResponse, Textarea, fastui_form
from httpx import AsyncClient
from pydantic import BaseModel, EmailStr, Field, SecretStr, field_validator
from pydantic_core import PydanticCustomError
# from shared import demo_page
@asynccontextmanager
async def lifespan(app_: FastAPI):
async with AsyncClient() as client:
app_.state.httpx_client = client
yield
router = dev_fastapi_app(lifespan=lifespan)
fastapi_auth_exception_handling(router)
FormKind: TypeAlias = Literal[ 'big']
def demo_page(*components: AnyComponent, title: str | None = None) -> list[AnyComponent]:
return [
c.PageTitle(text=f'FastUI Demo — {title}' if title else 'FastUI Demo'),
c.Navbar(
title='FastUI Demo',
title_event=GoToEvent(url='/'),
start_links=[
c.Link(
components=[c.Text(text='Components')],
on_click=GoToEvent(url='/components'),
active='startswith:/components',
),
c.Link(
components=[c.Text(text='Tables')],
on_click=GoToEvent(url='/table/cities'),
active='startswith:/table',
),
c.Link(
components=[c.Text(text='Auth')],
on_click=GoToEvent(url='/auth/login/password'),
active='startswith:/auth',
),
c.Link(
components=[c.Text(text='Forms')],
on_click=GoToEvent(url='/forms/login'),
active='startswith:/forms',
),
],
),
c.Page(
components=[
*((c.Heading(text=title),) if title else ()),
*components,
],
),
c.Footer(
extra_text='FastUI Demo',
links=[
c.Link(
components=[c.Text(text='Github')], on_click=GoToEvent(url='https://github.com/pydantic/FastUI')
),
c.Link(components=[c.Text(text='PyPI')], on_click=GoToEvent(url='https://pypi.org/project/fastui/')),
c.Link(components=[c.Text(text='NPM')], on_click=GoToEvent(url='https://www.npmjs.com/org/pydantic/')),
],
),
]
# %% Form class
class ClassesEnum(str, enum.Enum):
a = 'A'
b = 'B'
c = 'C'
class BigModel(BaseModel):
name: str | None = Field(None, description='This field is not required, it must start with a capital letter if provided')
select_multiple_or_one: list[ClassesEnum] = Field(title='Select class(es)', description="test")
@field_validator('name')
def name_validator(cls, v: str | None) -> str:
if v and v[0].islower():
raise PydanticCustomError('lower', 'Name must start with a capital letter')
return v
@field_validator('select_multiple_or_one', mode='before')
def correct_list_fields(cls, value: List[str] | str) -> List[str]:
print(value)
if isinstance(value, list):
if ClassesEnum.a in value and ClassesEnum.b in value :
raise PydanticCustomError('AB error', 'A and B can not be selectected together')
else:
return [value]
return value
# %% functions
@router.get('/api/content/{kind}', response_model=FastUI, response_model_exclude_none=True)
def forms_view(kind: FormKind) -> list[AnyComponent]:
return demo_page(
c.ServerLoad(
path='/forms/content/{kind}',
load_trigger=PageEvent(name='change-form'),
components=form_content(kind),
),
title='Forms',
)
@router.post('/api/forms_submit/big', response_model=FastUI, response_model_exclude_none=True)
async def big_form_post(form: Annotated[BigModel, fastui_form(BigModel)]):
print(form)
return [c.FireEvent(event=GoToEvent(url='/'))]
@router.get('/content/big', response_model=FastUI, response_model_exclude_none=True)
def form_content(kind: FormKind):
return [
c.Heading(text='Large Form', level=2),
c.Paragraph(text='Form with a lot of fields.'),
c.ModelForm(model=BigModel,
display_mode='page',
submit_url='/api/forms_submit/big'),
]
@router.get('/api/', response_model=FastUI, response_model_exclude_none=True)
def api_index() -> list[AnyComponent]:
# language=markdown
markdown = """\
* `ModelForm` — See [forms](/content/big)
"""
return demo_page(c.Markdown(text=markdown))
@router.get('/{path:path}')
async def html_landing() -> HTMLResponse:
return HTMLResponse(prebuilt_html(title='FastUI Demo'))
I noticed that the PydanticCustomError is present on the page. However, the message isn't being displayed.
<div class="invalid-feedback">A and B can not be selectected together</div>
See:
Is it possible to display this PydanticCustomError on the page?