Query parameters
Parameters not included in the Path are automatically treated as Query parameters.
- Required: Declared as function arguments without default values.
- Optional: Declared with default values.
import flask
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
@app.route('/items/')
def get_items(needy: str, skip: int = 0, limit: int = 100):
data = {
'needy': needy,
'skip': skip,
'limit': limit,
}
return flask.jsonify(data)
Validation:
needy
: Required and must be a string.skip
: Optional and must be an integer. If not included, it defaults to 0.limit
: Optional and must be an integer. If not included, it defaults to 100.
✅ Valid request: GET http://127.0.0.1:5000/items/?needy=passed&skip=20
{
"limit": 100,
"needy": "passed",
"skip": 20
}
❌ Bad request: If needy
is not included in the request http://127.0.0.1:5000/items/
{
"errors": [
{
"input": {},
"loc": [
"query",
"needy"
],
"msg": "Field required",
"type": "missing",
"url": "https://errors.pydantic.dev/2.9/v/missing"
}
]
}
Custom validations
You can apply additional validation using Pydantic's custom types with constraints, or define your own custom data types
import typing as t
import annotated_types as at
import flask
import pydantic
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
# Custom Types for additional validation combining Pydantic and Annotated
Needy = t.Annotated[str, at.MinLen(3), at.MaxLen(10)]
Limit = t.Annotated[int, at.Ge(1), at.Le(100), pydantic.Field(alias="size")]
@app.route('/items/')
def get_items(needy: Needy, skip: int = 0, limit: Limit = 100):
data = {
'needy': needy,
'skip': skip,
'limit': limit,
}
return flask.jsonify(data)
Alternatively, you can use the Query
field. This field is an extension of
Pydantic's field,
offering powerful validation capabilities.
This flexibility allows you to tailor query parameter validation to your application's specific needs.
Tip
The Query
field is supported aliasing. You can use the alias
argument to define
the query parameter name in the request.
import typing as t
import flask
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
Needy = t.Annotated[str, ftr.Query(min_length=3, max_length=10)]
Limit = t.Annotated[int, ftr.Query(ge=1, le=100, alias="size")]
@app.route('/items/')
def get_items(needy: Needy, skip: int = 0, limit: Limit = 100):
data = {
'needy': needy,
'skip': skip,
'limit': limit,
}
return flask.jsonify(data)
Validation:
needy
: Required and must be a string between 3 and 10 characters.skip
: Optional and must be an integer.limit
: Optional and must be an integer between 1 and 100, and must be namedsize
in the request.
Example request: GET http://127.0.0.1:5000/items/?needy=passed&size=20
{
"limit": 20,
"needy": "passed",
"skip": 0
}
Pydantic models
If you have a group of query parameters that are related, you can create a Pydantic model to declare them.
This would allow you to re-use the model in multiple places and also to declare validations and metadata for all the parameters at once.
import typing as t
import pydantic
import flask
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
class QueryParams(pydantic.BaseModel):
status: bool
skip: int = 0
limit: int = 10
tracking_number: t.Annotated[int, pydantic.Field(alias="tracking", le=3)] = 1
payment_method: t.Literal["cash", "credit"] = "credit"
@app.get('/orders/<user_id>/')
def get_orders(
user_id: int,
params: t.Annotated[QueryParams, ftr.Query()]
):
data = {'user_id': user_id, "params": params.model_dump()}
return flask.jsonify(data)
Warning
The Query
field can only be directly specified in the function signature.
When using Pydantic models, you must use Pydantic's fields.
Example request: GET ttp://127.0.0.1:5000/orders/233/?status=true
{
"params": {
"limit": 10,
"payment_method": "credit",
"skip": 0,
"status": true,
"tracking_number": 1
},
"user_id": 233
}
Parsing Arrays
If you want to allow a query parameter to parse as an Array, you can use set
, tuple
, or list
annotations.
Tip
You can use the set
type hint to validate that the values are unique.
import typing as t
import flask
import flask_typed_routes as ftr
import pydantic
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
Tags = t.Annotated[list[str], pydantic.Field(alias="tag")]
@app.get('/users/<user_id>/')
def get_users(user_id: int, tags: Tags = ()):
data = {'user_id': user_id, "tags": tags}
return flask.jsonify(data)
Example request: GET http://127.0.0.1:5000/users/123/?tag=hello&tag=world
{
"tags": [
"hello",
"world"
],
"user_id": 123
}
Note
It is important to highlight that the previous URL contains multiple query parameters named tag
.
Tip
If the URL includes a query parameter with multiple values separated by commas, pipes(|
), or spaces,
the resulting list will contain a single element with the entire string.
To retrieve each value separately, you need to set the explode
parameter in the Query
field and specify the style
parameter to define the serialization format.
Here’s an example of how to do it:
import typing as t
import flask
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
# By default, the 'style' is 'form', which means that the values are separated by commas.
TagsByComma = t.Annotated[list[str], ftr.Query(explode=False)]
# You can also use 'pipeDelimited' or 'spaceDelimited' as the 'style' to indicate another serialization style delimiter.
TagsBySpace = t.Annotated[list[str], ftr.Query(explode=False, style="spaceDelimited")]
TagsByPipe = t.Annotated[list[str], ftr.Query(explode=False, style="pipeDelimited")]
@app.get('/tags/comma/')
def get_tags_by_comma(tags: TagsByComma = ()):
return flask.jsonify({"tags": tags})
@app.get('/tags/space/')
def get_tags_by_space(tags: TagsBySpace = ()):
return flask.jsonify({"tags": tags})
@app.get('/tags/pipe/')
def get_tags_by_pipe(tags: TagsByPipe = ()):
return flask.jsonify({"tags": tags})
Example requests:
- By commas:
http://localhost:5000/tags/comma/?tags=hello,world
- By spaces:
http://localhost:5000/tags/space/?tags=hello world
- By pipes:
http://localhost:5000/tags/pipe/?tags=hello|world
You will see the JSON response as:
{
"tags": [
"hello",
"world"
]
}
Parsing Objects
Query parameters can be parsed as Objects using dictionaries or Pydantic models.
The library follows the form
style and of OpenAPI parameter serialization for objects.
The query parameters can be serialized in different ways using the style
and explode
parameters.
style | explode | URL |
---|---|---|
form (default) | true (default) | /users?role=admin&first_name=Alex |
form | false | /users?info=role,admin,first_name,Alex |
pipeDelimited | true/false | n/a |
pipeDelimited | true/false | n/a |
Parsing a single query parameter as an object
The explode
parameter should be set to False
to parse a single query parameter as an object using the form
style.
Warning
The supported styles are form
, spaceDelimited
, and pipeDelimited
, but only form
is supported for objects.
Using a dictionary
import typing as t
import flask
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
@app.get('/users/')
def get_users(info: t.Annotated[dict, ftr.Query(explode=False)]):
return flask.jsonify({'info': info})
Example request: GET http://127.0.0.1:5000/users/?info=role,admin,first_name,Alex
{
"info": {
"first_name": "Alex",
"role": "admin"
}
}
Using Pydantic models:
import typing as t
import flask
import pydantic
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
class UserInfo(pydantic.BaseModel):
role: str
first_name: str
class QueryParams(pydantic.BaseModel):
info: UserInfo
status: str = "default"
@app.get('/users/')
def get_users(params: t.Annotated[QueryParams, ftr.Query(explode=False)]):
return flask.jsonify(params.model_dump())
Example request: GET http://127.0.0.1:5000/users/?info=role,admin,first_name,Alex
{
"info": {
"first_name": "Alex",
"role": "admin"
},
"status": "default"
}
Parsing JSONs
You can parse JSON data from the query parameters using the Json
type wrapper of Pydantic.
import typing as t
import flask
import pydantic
import flask_typed_routes as ftr
app = flask.Flask(__name__)
ftr.FlaskTypedRoutes(app=app)
class UserInfo(pydantic.BaseModel):
role: str
first_name: str
class QueryParams(pydantic.BaseModel):
info: pydantic.Json[UserInfo]
@app.get('/users/signature/')
def get_users1(info: pydantic.Json[UserInfo]):
# Using directly the `Json` type in the function signature
return flask.jsonify(info.model_dump())
@app.get('/users/model/')
def get_users2(query: t.Annotated[QueryParams, ftr.Query()]):
# Using a Pydantic model with the `Json` type
return flask.jsonify(query.model_dump()['info'])
Example request:
GET http://127.0.0.1:5000/users/signature/?info={"role": "admin", "first_name": "Alex"}
GET http://127.0.0.1:5000/users/model/?info={"role": "admin", "first_name": "Alex"}
The response will be:
{
"first_name": "Alex",
"role": "admin"
}