Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FastAPI Single Parameter Body cause Pydantic Validation Error

I have a POST FastAPI method. I do not want to construct a class nor query string. So, I decide to apply Body() method.

@app.post("/test-single-int")
async def test_single_int(
    t: int = Body(...)
):
    pass

This is the request

POST http://localhost:8000/test-single-int/

{
  "t": 10
}

And this is the response

HTTP/1.1 422 Unprocessable Entity
date: Fri, 22 May 2020 10:00:16 GMT
server: uvicorn
content-length: 83
content-type: application/json
connection: close

{
  "detail": [
    {
      "loc": [
        "body",
        "s"
      ],
      "msg": "str type expected",
      "type": "type_error.str"
    }
  ]
}

However, after trying with many samples, I found that they will not error if I have more than one Body(). For example,

@app.post("/test-multi-mix")
async def test_multi_param(
    s: str = Body(...),
    t: int = Body(...),
):
    pass

Request

POST http://localhost:8000/test-multi-mix/

{
  "s": "test",
  "t": 10
}

Response

HTTP/1.1 200 OK
date: Fri, 22 May 2020 10:16:12 GMT
server: uvicorn
content-length: 4
content-type: application/json
connection: close

null

Does anyone have any idea about my implementation? Are there wrong? Is it not best practice? Or it is a bug?

like image 598
Pranithan T. Avatar asked May 22 '20 10:05

Pranithan T.


1 Answers

It is not a bug, it is how Body behaves, it exists for "extending" request params how documentation outlines:

class Item(BaseModel):
    name: str

class User(BaseModel):
    username: str
    full_name: str = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item,
    user: User,
    importance: int = Body(..., gt=0),
    q: str = None
):
    pass

Valid request body for this view would be:

{
    "item": {
        "name": "Foo",
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    },
    "importance": 5
}

If you really want to use Body alone you must specify embed=True, this one works as expected:

@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id:int,
    importance: int = Body(..., gt=0, embed=True),
    q: str = None
):
    pass
like image 178
funnydman Avatar answered Nov 16 '22 23:11

funnydman