10

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'

1 Answer 1

2

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.

Working Example

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
Sign up to request clarification or add additional context in comments.

1 Comment

Interesting, Chris --thanks for the input. I'll give this some thought, it isn't an exact match but may be close enough to be usable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.