---
title: "💭 python - How to use a Pydantic model with Form data in FastAPI..."
description: "!https://stackoverflow.com/questions/60127234/how-to-use-a-pydantic-model-with-form-data-in-fastapi"
date: 2023-08-08
published: true
tags:
  - python
  - fastapi
  - htmx
  - pydantic
  - webdev
  - thought
template: link
---


<div class="embed-card embed-card-external">
  <a href="https://stackoverflow.com/questions/60127234/how-to-use-a-pydantic-model-with-form-data-in-fastapi" class="embed-card-link" target="_blank" rel="noopener noreferrer">
    <div class="embed-card-content">
      <div class="embed-card-title">External Link</div>
      <div class="embed-card-meta">stackoverflow.com</div>
    </div>
  </a>
</div>


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


``` python
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


``` python
@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 <a href="/thoughts/" class="wikilink" data-title="Thoughts" data-description="These are generally my thoughts on a web page or some sort of url, except a rare few don&#39;t have a link. These are dual published off of my..." data-date="2024-04-01">thought</a>. It's a short note that I make
    about someone else's content online <a href="/tags/thoughts/" class="hashtag-tag" data-tag="thoughts" data-count=2 data-reading-time=3 data-reading-time-text="3 minutes">#thoughts</a>
