The query parameter q is of type Union[str, None] (or str | None in Python 3.10), that means that it's of type str but could also be None, and indeed, the default value is None, so FastAPI will know it's not required.
Note
FastAPI will know that the value of q is not required because of the default value = None.
The Union in Union[str, None] will allow your editor to give you better support and detect errors.
Notice that the default value is still None, so the parameter is still optional.
But now, having Query(max_length=50) inside of Annotated, we are telling FastAPI that we want it to extract this value from the query parameters (this would have been the default anyway 🤷) and that we want to have additional validation for this value (that's why we do this, to get the additional validation). 😎
FastAPI will now:
Validate the data making sure that the max length is 50 characters
Show a clear error for the client when the data is not valid
Document the parameter in the OpenAPI schema path operation (so it will show up in the automatic docs UI)
Previous versions of FastAPI (before 0.95.0) required you to use Query as the default value of your parameter, instead of putting it in Annotated, there's a high chance that you will see code using it around, so I'll explain it to you.
Tip
For new code and whenever possible, use Annotated as explained above. There are multiple advantages (explained below) and no disadvantages. 🍰
This is how you would use Query() as the default value of your function parameter, setting the parameter max_length to 50:
As in this case (without using Annotated) we have to replace the default value None in the function with Query(), we now need to set the default value with the parameter Query(default=None), it serves the same purpose of defining that default value (at least for FastAPI).
So:
q:Union[str,None]=Query(default=None)
...makes the parameter optional, with a default value of None, the same as:
q:Union[str,None]=None
And in Python 3.10 and above:
q:str|None=Query(default=None)
...makes the parameter optional, with a default value of None, the same as:
q:str|None=None
But it declares it explicitly as being a query parameter.
Info
Keep in mind that the most important part to make a parameter optional is the part:
=None
or the:
=Query(default=None)
as it will use that None as the default value, and that way make the parameter not required.
The Union[str, None] part allows your editor to provide better support, but it is not what tells FastAPI that this parameter is not required.
Then, we can pass more parameters to Query. In this case, the max_length parameter that applies to strings:
Using Annotated is recommended instead of the default value in function parameters, it is better for multiple reasons. 🤓
The default value of the function parameter is the actual default value, that's more intuitive with Python in general. 😌
You could call that same function in other places without FastAPI, and it would work as expected. If there's a required parameter (without a default value), your editor will let you know with an error, Python will also complain if you run it without passing the required parameter.
When you don't use Annotated and instead use the (old) default value style, if you call that function without FastAPI in other place, you have to remember to pass the arguments to the function for it to work correctly, otherwise the values will be different from what you expect (e.g. QueryInfo or something similar instead of str). And your editor won't complain, and Python won't complain running that function, only when the operations inside error out.
Because Annotated can have more than one metadata annotation, you could now even use the same function with other tools, like Typer. 🚀
This specific regular expression pattern checks that the received parameter value:
^: starts with the following characters, doesn't have characters before.
fixedquery: has the exact value fixedquery.
$: ends there, doesn't have any more characters after fixedquery.
If you feel lost with all these "regular expression" ideas, don't worry. They are a hard topic for many people. You can still do a lot of stuff without needing regular expressions yet.
But whenever you need them and go and learn them, know that you can already use them directly in FastAPI.
Pydantic, which is what powers all the data validation and serialization in FastAPI, has a special behavior when you use Optional or Union[Something, None] without a default value, you can read more about it in the Pydantic docs about Required Optional fields.
Tip
Remember that in most of the cases, when something is required, you can simply omit the default, so you normally don't have to use ....
When you define a query parameter explicitly with Query you can also declare it to receive a list of values, or said in other way, to receive multiple values.
For example, to declare a query parameter q that can appear multiple times in the URL, you can write:
you would receive the multiple qquery parameters' values (foo and bar) in a Python list inside your path operation function, in the function parameterq.
So, the response to that URL would be:
{"q":["foo","bar"]}
Tip
To declare a query parameter with a type of list, like in the example above, you need to explicitly use Query, otherwise it would be interpreted as a request body.
The interactive API docs will update accordingly, to allow multiple values:
Query parameter list / multiple values with defaults¶
And you can also define a default list of values if none are provided:
That information will be included in the generated OpenAPI and used by the documentation user interfaces and external tools.
Note
Keep in mind that different tools might have different levels of OpenAPI support.
Some of them might not show all the extra information declared yet, although in most of the cases, the missing feature is already planned for development.
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
fromtypingimportAnnotated,UnionfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[Union[str,None],Query(title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
fromtypingimportUnionfromfastapiimportFastAPI,Queryfromtyping_extensionsimportAnnotatedapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[Union[str,None],Query(title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
Tip
Prefer to use the Annotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
Tip
Prefer to use the Annotated version if possible.
fromtypingimportUnionfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Union[str,None]=Query(default=None,title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
Now let's say you don't like this parameter anymore.
You have to leave it there a while because there are clients using it, but you want the docs to clearly show it as deprecated.
Then pass the parameter deprecated=True to Query:
fromtypingimportAnnotatedfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[str|None,Query(alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
fromtypingimportAnnotated,UnionfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[Union[str,None],Query(alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
fromtypingimportUnionfromfastapiimportFastAPI,Queryfromtyping_extensionsimportAnnotatedapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Annotated[Union[str,None],Query(alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),]=None,):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
Tip
Prefer to use the Annotated version if possible.
fromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:str|None=Query(default=None,alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
Tip
Prefer to use the Annotated version if possible.
fromtypingimportUnionfromfastapiimportFastAPI,Queryapp=FastAPI()@app.get("/items/")asyncdefread_items(q:Union[str,None]=Query(default=None,alias="item-query",title="Query string",description="Query string for the items to search in the database that have a good match",min_length=3,max_length=50,pattern="^fixedquery$",deprecated=True,),):results={"items":[{"item_id":"Foo"},{"item_id":"Bar"}]}ifq:results.update({"q":q})returnresults
To exclude a query parameter from the generated OpenAPI schema (and thus, from the automatic documentation systems), set the parameter include_in_schema of Query to False: