DEV Community

Cover image for Multi-value Query Parameters with Flask
Sven Cowart
Sven Cowart

Posted on

Multi-value Query Parameters with Flask

Super Short Intro to Flask

Flask is a microframework for building Python applications. It can be used to build RESTful APIs.

Framing the Problem

Often, RESTful APIs have GET routes for fetching documents. For example, GET http://localhost:5000/users will return a list of all user documents in its response. A general convention to filter the results of this request is to append a query string such as GET http://localhost:5000/users?type=jedi&species=human. This request will return all users that are of type Jedi and human. Additionally, a single query parameter could have more than one value like GET http://localhost:5000/users?type=jedi&type=sith&species=human. This request will return all users that are Jedi or Sith and human. (Other formats of providing multiple values to a single query parameter are used in practice but are not supported by Flask. One of these formats is /users?type=jedi,sith.) With Flask, query parameters are retrieved by accessing the request.args property within a route definition:

@app.route('/users')
def fetch_users():
    query_params = request.args
    .
    .
    .
Enter fullscreen mode Exit fullscreen mode

The Problem

If the query string has multi-value parameters like ?type=jedi&type=sith&species=human, then the value returned by request.args.to_dict() is:

{
  "type": "jedi",
  "species": "human"
}
Enter fullscreen mode Exit fullscreen mode

Wait, what happened to the second value for type? Flask by default assumes query parameters to be of single-value.

The Solution

The default behavior can be modified by passing flat=False into to_dict() like request.args.to_dict(flat=False). This returns all values for each parameter as a list, instead of only returning the first value. Unfortunately, parameters with only one value also have this behavior applied, which means the output of the above example is:

{
  "type": ["jedi", "sith"],
  "species": ["human"]
}
Enter fullscreen mode Exit fullscreen mode

At this point, all the values for each parameter are accessible, but it would be convenient if the parameters with only a single value are not converted to lists. We can implement this behavior with the following code:

(Notice, parts of the Flask implementation are intentially omitted for brevity.)

from flask import Flask, request

def normalize_query_param(value):
    """
    Given a non-flattened query parameter value,
    and if the value is a list only containing 1 item,
    then the value is flattened.

    :param value: a value from a query parameter
    :return: a normalized query parameter value
    """
    return value if len(value) > 1 else value[0]


def normalize_query(params):
    """
    Converts query parameters from only containing one value for each parameter,
    to include parameters with multiple values as lists.

    :param params: a flask query parameters data structure
    :return: a dict of normalized query parameters
    """
    params_non_flat = params.to_dict(flat=False)
    return {k: normalize_query_param(v) for k, v in params_non_flat.items()}


@app.route('/users')
def fetch_users():
    query_params = normalize_query(request.args)
    .
    .
    .
Enter fullscreen mode Exit fullscreen mode

Here, the query parameters are retrieved using request.args.to_dict(flat=False) — then we iterate through each parameter. If the parameter value contains more than one value, the unmodified list is returned to the parameter, otherwise, its single value is returned. The result for the following query string ?type=jedi&type=sith&species=human will be:

{
  "type": ["jedi", "sith"],
  "species": "human"
}
Enter fullscreen mode Exit fullscreen mode

Now, all multi-value query parameters are represented by lists, while maintaining the expected original behavior for single-value query parameters.

I hope you learned something. Let me know what you think in the comments.

Prost!

Top comments (0)