shot of post - 💭 python - How to use a Pydantic model with Form data in FastAPI...

Here's my thought on 💭 python - How to use a Pydantic model with Form data in FastAPI...


I went down the route of leveraging the json-enc extention in htmx , but later realized that this completely breaks browsers/users who do not wish to use javascript. While most of the web would feel quite broken with javascript disabled, I don't want to contribute to that without good reason.

Taking a second look into this issue, rather than using json-enc, and using as_form to get form data into a model keeps the nice DX fo everything being a pydantic model, but the site still works without js. with js htmx kicks in, you get a spa like experience by loading partials onto the page, and without, you just get a full page reload.

the implementation

copied from https://stackoverflow.com/questions/60127234/how-to-use-a-pydantic-model-with-form-data-in-fastapi

import inspect
from typing import Type

from fastapi import Form from pydantic import BaseModel from pydantic.fields import ModelField

def as_form(cls: Type[BaseModel]): new_parameters = []

for field_name, model_field in cls.__fields__.items():
    model_field: ModelField  # type: ignore

    new_parameters.append(
         inspect.Parameter(
             model_field.alias,
             inspect.Parameter.POSITIONAL_ONLY,
             default=Form(...) if model_field.required else Form(model_field.default),
             annotation=model_field.outer_type_,
         )
     )

async def as_form_func(**data):
    return cls(**data)

sig = inspect.signature(as_form_func)
sig = sig.replace(parameters=new_parameters)
as_form_func.__signature__ = sig  # type: ignore
setattr(cls, 'as_form', as_form_func)
return cls

And the usage looks like

@as_form
class Test(BaseModel):
    param: str
    a: int = 1
    b: str = '2342'
    c: bool = False
    d: Optional[float] = None

@router.post('/me', response_model=Test) async def me(request: Request, form: Test = Depends(Test.as_form)): return form


!!! note This post is a thought. It's a short note that I make about someone else's content online. Learn more about the process [[ thoughts ]]

{.text-opacity-80} This post was a thought by Waylon Walker see all my thoughts at https://waylonwalker.com/thoughts