9

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.

Sam Mason
  • 15,216
  • 1
  • 41
  • 60

1 Answers1

13

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

Gabriel Cappelli
  • 3,632
  • 1
  • 17
  • 31