Arthur Ávila
Arthur Ávila

Reputation: 69

How to send a request with multiple (List) data values in FastAPI?

I'm trying to create an API using FastAPI + MongoDB that can return multiple values from the request. The MongoDB is full of data and using mongoengine I can consult one or all data when sending a request to the specific endpoint. What I'm trying to do now is receive one or more data from an endpoint, for example:

When consulting the endpoint http://127.0.0.1:8000/rice I get a JSON response with all the data from this item that is on MongoDB. But the problem is that I need to have this endpoint consulting one or more data from MongoDB and return as much data as the user sends to the endpoint, for example: http://127.0.0.1:8000/rice&pasta&beanand return the JSON with the information that it is in the MongoDB about rice, pasta and bean.

In the code I have a main.py with the routes like:

@app.get('/{description}', status_code=200)
def get_description(description):
    return JSONResponse(TabelaService().get_description(description))

This function calls another function that call another function that use queryset to consult the data from MongoDB and serialize it:

    def get_description(self, description):
        try:
            description = TabelaNutricional.get_by_description(description)
            return self.serialize(description)
        except:
            raise DescriptionNotFound

And bellow is the function who get the data from MongoDB:

    @queryset_manager
    def get_by_description(doc_cls, queryset, description):
        nutriente = queryset(description=str(description)).get()
        return nutriente

Does anyone has a clue to how to get more data in the endpoint? Thank you!

Upvotes: 1

Views: 5733

Answers (1)

Chris
Chris

Reputation: 34551

Option 1 - Use List of Query parameters

You can declare a query parameter with a type of List and explicitly use Query, as demonstrated in this and this answer, and as explained in the documentation. In this way, you can receive multiple values for the query parameter, for instance:

http://127.0.0.1:8000/?items=rice&items=pasta&items=bean 

On server side, you can loop through the list of items and call your function for each item in the list, and create a dictionary with the results to send back to the client (see related answer). For instance:

from fastapi import Query
from typing import List

@app.get('/', status_code=200)
def get_description(items: List[str] = Query(...)):
    data = {}
    for i in items:
        d = TabelaService().get_description(i)
        data[i] = d
        
    return JSONResponse(data)

Option 2 - Use List of Form parameters

You could send a list of Form parameters in the request body instead. Have a look at related answers here and here.

from fastapi import Form
from typing import List

@app.post('/')
async def submit(items: List[str] = Form(...)):
    data = {}
    for i in items:
        # ...
    return JSONResponse(data)
Test using Python requests
import requests

url = 'http://127.0.0.1:8000/'
data = {'items': ['foo', 'bar']}
r = requests.post(url, data=data)              
print(r.text)

Option 3 - Use List of Body parameters / JSON data

You could alternatively send the data encoded as application/json data in the request body. Since when defining a parameter with List in FastAPI endpoint, and that parameter is not a path parameter, it is automatically considered as a body parameter, i.e., expecting JSON data. If it wasn't defined with List, you would need to use the Body type or one of the other options demonstrated in this answer to defined that parameter as a JSON body parameter.

from typing import List

@app.post('/')
async def submit(items: List[str]):
    data = {}
    for i in items:
        # ...
    return JSONResponse(data)
Test using Python requests
import requests

url = 'http://127.0.0.1:8000/'
payload = ['foo', 'bar']
r = requests.post(url, json=payload)              
print(r.text)

In case you would like passing the list of data using the items key on client side, you should use the special Body parameter embed. This is required, in case you only had a single body parameter defined in the endpoint. Example:

from fastapi import Body

@app.post('/')
async def submit(items: List[str] = Body(..., embed=True)):
    pass
Test using Python requests
# ...
payload ={'items': ['foo', 'bar']}
r = requests.post(url, json=payload) 

If, however, you had more than one body parameters defiend in the endpoint, FastAPI would automatically expect you to post the JSON body parameters using each of the key names defined in the endpoint. Example:

@app.post('/')
async def submit(items: List[str], colours: List[str]):
    pass
Test using Python requests
# ...
payload ={'items': ['foo', 'bar'], 'colours': ['white', 'blue']}
r = requests.post(url, json=payload) 

Option 4 - Use single path parameter (Not Recommended)

If you still want to use a Path parameter instead, you can use the below and call it as shown in your question i.e., http://127.0.0.1:8000/rice&pasta&bean. See this related answer, which explains how to have / characters in that single path parameter as well, if required.

@app.get('/{items}', status_code=200)
def get_description(items: str):
    items = items.split('&')
    data = {}
    for i in items:
        d = TabelaService().get_description(i)
        data[i] = d
        
    return JSONResponse(data)

Note: If you used the above option (i.e., using a path parameter), the Swagger UI autodocs (that can be accessed at http://127.0.0.1:8000/docs) wouldn't load. That's because when accessing that URL, the endpoint above is called, and takes docs as the value for the items path parameter (see related answers here, as well as here and here). Thus, you can either add an extra path on that endpoint, e.g., @app.get('/api/{items}' and call it using, for instance, http://127.0.0.1:8000/api/rice&pasta&bean—.thus, allowing /docs to load successfully—or simply use one of the remaining approaches demonstrated above.

Upvotes: 1

Related Questions