Reputation: 3323
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
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