Sam Mason
Sam Mason

Reputation: 16214

supporting both form and json encoded bodys with FastAPI

I've been using FastAPI to create an HTTP based API. It currently supports JSON encoded parameters, but I'd also like to support form-urlencoded (and ideally even form-data) parameters at the same URL.

Following on Nikita's answer I can get separate urls working with:

from typing import Optional
from fastapi import FastAPI, Body, Form, Depends
from pydantic import BaseModel

class MyItem(BaseModel):
    id: Optional[int] = None
    txt: str

    @classmethod
    def as_form(cls, id: Optional[int] = Form(None), txt: str = Form(...)) -> 'MyItem':
        return cls(id=id, txt=txt)

app = FastAPI()

@app.post("/form")
async def form_endpoint(item: MyItem = Depends(MyItem.as_form)):
    print("got item =", repr(item))
    return "ok"

@app.post("/json")
async def json_endpoint(item: MyItem = Body(...)):
    print("got item =", repr(item))
    return "ok"

and I can test these using curl by doing:

curl -X POST "http://localhost:8000/form" -d 'txt=test'

and

curl -sS -X POST "http://localhost:8000/json" -H "Content-Type: application/json" -d '{"txt":"test"}'

It seems like it would be nicer to have a single URL that accepts both content-types and have the model parsed out appropriately. But the above code currently fails with either:

{"detail":[{"loc":["body","txt"],"msg":"field required","type":"value_error.missing"}]}

or

{"detail":"There was an error parsing the body"}

if I post to the "wrong" endpoint, e.g. form encoding posted to /json.

For bonus points; I'd also like to support form-data encoded parameters as it seems related (my txt can get rather long in practice), but might need to turn it into another question if it's sufficiently different.

Upvotes: 10

Views: 8027

Answers (1)

Gabriel Cappelli
Gabriel Cappelli

Reputation: 4190

FastAPI can't route based on Content Type, you'd have to check that in the request and parse appropriately:

@app.post('/')
async def route(req: Request) -> Response:
    if req.headers['Content-Type'] == 'application/json':
        item = MyItem(** await req.json())
    elif req.headers['Content-Type'] == 'multipart/form-data':
        item = MyItem(** await req.form())
    elif req.headers['Content-Type'] == 'application/x-www-form-urlencoded':
        item = MyItem(** await req.form())
    return Response(content=item.json())

There seems to be an open issue on GitHub regarding this functionality

Upvotes: 14

Related Questions