pythonlistpython-requestsfastapihttp-status-code-422

FastAPI POST request with List input raises 422 Unprocessable Entity error


I would like to write a POST request in which an input parameter is a list, but I got error 422 unprocessable entity:

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

My POST request is:

@router.post('',status_code=200)
def register(reg_id: int, reg_name: str, reg_option_list:List[int]):
    reg_item = My_DB(
        id=reg_id,
        name=reg_name,
        option_list=reg_option_list,
    )
    item = db.query(My_DB).filter(My_DB.id == service_id).first()

    if item is not None:
        raise HTTPException(status_code=400, detail="Item exists.")
    db.add(reg_item)
    db.commit()
    return reg_item

But when I change my code like below, remove list input and set the value in code as a list, everything works fine:

@router.post('',status_code=200)
def register(reg_id: int, reg_name: str,):
    reg_item = My_DB(
        id=reg_id,
        name=reg_name,
        option_list=[1,2,3],
    )
    item = db.query(My_DB).filter(My_DB.id == service_id).first()

    if item is not None:
        raise HTTPException(status_code=400, detail="Item exists.")
    db.add(reg_item)
    db.commit()
    return reg_item

I will appreciate any help about my list input parameter. Thanks.


Solution

  • As per the documentation (have a look under the "Tip" section)

    To declare a query parameter with a type of list, like in the example above, you need to explicitly use Query, otherwise it would be interpreted as a request body.

    Thus, by declaring a List parameter in the way you do, the endpoint will expect to receive it in the request body, rather than in the query string as a query parameter. Hence, a 422 unprocessable entity error is raised, including the specific details you provided (i.e., body field is missing). You could also confirm that through OpenAPI/Swagger UI at /docs, for instance, http://127.0.0.1:8000/docs. You would see that the value for reg_option_list is expected to be passed to the Request body section, not in the query parameters.

    Option 1 - Send List of data in the query string

    The way to do this is to define the query parameter explicitly with Query; thus, allowing the parameter to appear multiple times in the URL. Since such a request would not include a request body, you should rather use a GET request, which is used when one requests data from the server, whereas POST is used to send data to the server stored in the request body.

    Example

    from fastapi import FastAPI, Query
    from typing import List
    
    
    app = FastAPI()
    
    
    @app.get('/')
    def register(reg_id: int, reg_name: str, reg_options: List[int] = Query(...)):
        return reg_options
    

    Test URL with Query parameters List:

    http://127.0.0.1:8000/?reg_id=1&reg_name=foo&reg_options=1&reg_options=2&reg_options=3
    

    Test using Python requests:

    import requests
    
    url = 'http://127.0.0.1:8000/?reg_id=1&reg_name=foo&reg_options=1&reg_options=2&reg_options=3'
    r = requests.get(url)
    print(r.text)
    

    or, preferably:

    import requests
    
    url = 'http://127.0.0.1:8000/'
    params = {'reg_id': 1, 'reg_name': 'foo', 'reg_options': [1, 2, 3]}
    r = requests.get(url, params=params)
    print(r.text)
    

    Option 2 - Send List of data in the request body

    If you would like to use a POST request and send the data in the request body instead, you could simply define the reg_options parameter above in the same way, but without using the = Query(...) part. This should tell FastAPI to expect a list of data for that endpoint in the body of the request. For more details and examples on how to send data in the request body to a FastAPI backend, please have a look here, as well as here and here. Further related answers can be found here, here, as well as here and here.