SteveJ
SteveJ

Reputation: 3323

How to allow any arbitrary query parameters using FastAPI and Swagger?

Note: This question is different from the one here, in that I need it to work with Swagger.

Given a FastAPI GET endpoint, I want to allow any arbitrary set of URL parameters, while maintaining Swagger support.

My use case is that I want to support a JSON API-like set of query parameters such as this:

/api/books/?include=author&sort=name,zip&sort[author]=-lname&fields=name,phone,street

The use of square brackets prevents me from using traditional classes to model query parameters, so I'm directly using the Request object instead. However, I would like to use Swagger to test the endpoint. I can't find a way to provide arbitrary URL parameters. I'm happy to type them in as a single string.

One would think something like the following:

def books(**params): 
    ....

That gives a curl statement of:

api/books?params=sort%5Bone%5D%3Dtwo'

what I really want is:

api/books?sort&one%5D%3Dtwo'

Upvotes: 10

Views: 4267

Answers (1)

Chris
Chris

Reputation: 34590

You could use an Optional string parameter (such as book_params in the example below) to pass the query parameters as a single string through OpenAPI (Swagger UI) e.g., include=author&sort=name,zip&sort[author]=-lname&fields=name,phone,street. You could then parse the query data (using urllib.parse.parse_qs) to get a dictionary, as shown below.

The below example also utilizes the method described here, in order to fix the part where parse_qs parses single values into lists (e.g., 'foo=bar' would be parsed into foo = ['bar']), while also preserving all the values for keys that the user passes a list. For example, if the user passed the same key multiple times in the URL, that is, for instance, 'foo=2&bar=7&foo=10', using dict(request.query_params) to retrieve the query parameters would result in {"foo":"10","bar":"7"} instead of {"foo":["2","10"],"bar":"7"}. The approach, however, demonstrated in the example below (using the aforementioned method) takes care of that as well, by parsing the query string (which can be retieved using request.url.query) and makes sure that actual lists are preserved.

You can check whether this optional parameter, i.e., book_params, is empty or not to decide whether to read the query params using book_params (meaning that the request is sent through Swagger) or using the Request object directly (meaning that the request is sent through typing the URL into the address bar of the browser e.g., http://127.0.0.1:8000/api/books?include=author&sort=name,zip&sort[author]=-lname&fields=name,phone,street, or using some other client app). Please make sure to name that optional parameter (i.e., book_params) something unique, which wouldn't also be part of the actual parameters.

from fastapi import FastAPI, Request
from typing import Optional
from urllib.parse import parse_qs

app = FastAPI()

@app.get("/api/books")
def books(request: Request, book_params: Optional[str] = None):
    q_params = {}
    
    if book_params is not None:
        q_params = parse_qs(book_params, keep_blank_values=True)
    else:   
        q_params = parse_qs(request.url.query, keep_blank_values=True)
        
    d = dict((k, v if len(v)>1 else v[0]) 
                for k, v in q_params.items())

    return d

Upvotes: 2

Related Questions