I have a problem with a REST API endpoint in FastAPI that accepts a list of strings via a single query parameter. An example of this endpoint's usage is:
http://127.0.0.1:8000/items/2?short=false&response=this&response=that
Here, the parameter named 'response' accepts a list of strings as documented in FastAPI tutorial, section on Query Parameters and String Validation. The endpoint works as expected in the browser.
However, it does not work in Swagger docs. The button labeled 'Add string item' shakes upon clicking 'Execute' to test the endpoint. Swagger UI seems unable to create the expected URL with the embedded query parameters (as shown in Fig 1.).
The code for the endpoint is as follows. I have tried with and without validation.
@app.get("/items/{item_ID}")
async def getQuestion_byID(item_ID: int = Path(
...,
title = "Numeric ID of the question",
description = "Specify a number between 1 and 999",
ge = 1,
le = 999
), response: Optional[List[str]] = Query(
[],
title="Furnish an answer",
description="Answer can only have letters of the alphabet and is case-insensitive",
min_length=3,
max_length=99,
regex="^[a-zA-Z]+$"
), short: bool = Query(
False,
title="Set flag for short result",
description="Acceptable values are 1, True, true, on, yes"
)):
"""
Returns the quiz question or the result.
Accepts item ID as path parameter and
optionally response as query parameter.
Returns result when the response is passed with the item ID.
Otherwise, returns the quiz question.
"""
item = question_bank.get(item_ID, None)
if not item:
return {"question": None}
if response:
return evaluate_response(item_ID, response, short)
else:
return {"question": item["question"]}
Grateful for any help.
As described here, this happens due to that OpenAPI applies the pattern
(as well as minimum
and maximum
constraints) to the schema of the array
itself, not just the individual items
in the array. If you checked the OpenAPI schema at http://127.0.0.1:8000/openapi.json, you would see that the schema for the response
parameter appears as shown below (i.e., validations are being applied to the array
itself as well):
{
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"required": false,
"schema": {
"title": "Furnish an answer",
"maxLength": 99,
"minLength": 3,
"pattern": "^[a-zA-Z]+$",
"type": "array",
"items": {
"maxLength": 99,
"minLength": 3,
"pattern": "^[a-zA-Z]+$",
"type": "string"
},
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"default": []
},
"name": "response",
"in": "query"
}
As mentioned here, you could use a Pydantic constr
instead to specify items
with that contraint:
my_constr = constr(regex="^[a-zA-Z]+$", min_length=3, max_length=99)
response: Optional[List[my_constr]] = Query([], title="Furnish an...", description="Answer can...")
Keep your response
parameter as is. Copy the OpenAPI schema from http://127.0.0.1:8000/openapi.json, remove the pattern
(as well as minimum
and maximum
attributes) from response
's (array
) schema and save the OpenAPI schema to a new file (e.g., my_openapi.json
). It should look like this:
...
{
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"required": false,
"schema": {
"title": "Furnish an answer",
"type": "array",
"items": {
"maxLength": 99,
"minLength": 3,
"pattern": "^[a-zA-Z]+$",
"type": "string"
},
"description": "Answer can only have letters of the alphabet and is case-insensitive",
"default": []
},
"name": "response",
"in": "query"
},
...
Then, in your app, instruct FastAPI to use that schema instead:
import json
app.openapi_schema = json.load(open("my_openapi.json"))
Since the above solution would require you to copy and edit the schema every time you make a change or add new endpoints/parameters, you would rather modify the OpenAPI schema as described here. This would save you from copying/editing the schema file. Make sure to add the below at the end of your code (after defining all the routes).
from fastapi.openapi.utils import get_openapi
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="FastAPI",
version="0.1.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
del openapi_schema["paths"]["/items/{item_ID}"]["get"]["parameters"][1]["schema"]["maxLength"]
del openapi_schema["paths"]["/items/{item_ID}"]["get"]["parameters"][1]["schema"]["minLength"]
del openapi_schema["paths"]["/items/{item_ID}"]["get"]["parameters"][1]["schema"]["pattern"]
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
In all the above solutions, the constraints annotation that would normally be shown in OpenAPI under response
(i.e., (query) maxLength: 99 minLength: 3 pattern: ^[a-zA-Z]+$
), won't appear (since Swagger would create that annotation from the constraints applied to the array
, not the items
), but there doesn't seem to be a way to preserve that. In Solutions 2 and 3, however, you could modify the "in"
attribute, shown in the JSON
code snippet above, to manually ddd the annotation. But, as HTML
elements, etc., are controlled by Swagger, the whole annotation would appear inside parentheses and without line breaks between the constraints. Nevertheless, you could still inform users about the constraints applied to items
, by specifying them in the description
of your Query
parameter.