# Query Parameters

Up until now we made an example without the possibility of us sending along any parameters with our API call. Time to expand our knowledge of APIs and look at query parameters.

In the example below, the path operation function contains a parameter called species.






 


from fastapi import FastAPI

app = FastAPI()

@app.get("/animal")
def get_animal(species):
    return {"my favorite animal": species}
1
2
3
4
5
6
7

The query is the set of key-value pairs that go after the ? in a URL, separated by & characters.

For example, the endpoint we created in the example above can be addressed with the following URL:

http://127.0.0.1:8000/animal?species=giraffe (opens new window)

In our URL, we have one query parameter called species with a value of giraffe. It is not a coincidence that the name of the query parameter in our URL perfectly matches the name of the parameter in our path operation function in Python. The FastAPI framework matches incoming parameters with parameters in our path operation function based on their name.

Query parameters are sent to the API as strings, but when you have declared them with Python types in your code, they are automatically converted to that type and validated against it. FastAPI handles errors when the data that is coming in does not match the data type you declared with the parameter in the path operation function.

In the example below, we have a query parameter called number which is declared with the data type int. The name of the parameter and its data type are seperated by a colon (:).








 


from fastapi import FastAPI

app = FastAPI()

courses = ["Programming Essentials", "Webdesign Essentials", "Data Essentials", "Full Stack Essentials", "Cloud & DevOps"]

@app.get("/favorite")
def get_favorite(number: int):
    return {"favorite course": courses[number]}
1
2
3
4
5
6
7
8
9

If you address this endpoint using the following URL:

http://127.0.0.1:8000/favorite?number=3 (opens new window)

... the course with index 3 in the courses list will be taken and returned as value of the favorite course key. So the result of calling the URL above would be the following:

{
    "favorite course": "Full Stack Essentials"
}
1
2
3

# Data validation

As mentioned before, FastAPI handles errors when the data that is coming in does not match the data type you declared with the parameter in the path operation function.

Let's take our last example again and send a request as follows:

http://127.0.0.1:8000/favorite?number=three (opens new window)

You will notice that an error is given in the form of a JSON document. In our specific case, the error that FastAPI gives as response to the request looks like this:

{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "query",
                "number"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "three",
            "url": "https://errors.pydantic.dev/2.5/v/int_parsing"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Let's interpret the message.

  • The value of the type key is int_parsing, indicating that a problem occurred when FastAPI tried to parse the incoming string to an integer.
  • The value of the loc key indicates where it went wrong. In this case we can see that the query parameter number could not be parsed.
  • The msg key gives a message that's easier to read.
  • The input key's value tells you what the actual value was that FastAPI tried to parse into an integer.
  • The url key gives you a link to general documentation about the error that occurred.

Of course, you can (and should) also write your own logic to prevent errors from happening. In our previous example, an error could be thrown by Python if we use an index number that is equal to or higher than the length of our list. To prevent such a problem from happening, we could add a little bit of logic, as shown below. In the example below, an if-else structure was added that will check whether the given number is greater than or equal to the length of the courses list. If that is the case, "no valid course was chosen" will be taken as value for the favorite course key in the dictionary that's returned.









 
 
 
 
 

from fastapi import FastAPI

app = FastAPI()

courses = ["Programming Essentials", "Webdesign Essentials", "Data Essentials", "Full Stack Essentials", "Cloud & DevOps"]

@app.get("/favorite")
def get_favorite(number: int):
    if number >= len(courses):
        course = "no valid course was chosen"
    else:
        course = courses[number]
    return {"favorite course": course}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Default values

As query parameters are not a fixed part of a path, they can be optional and can have default values.

In the example below the query parameter number received a default value of 0. This is done by adding a value behind the parameter, seperated by an equal sign.








 






from fastapi import FastAPI

app = FastAPI()

courses = ["Programming Essentials", "Webdesign Essentials", "Data Essentials", "Full Stack Essentials", "Cloud & DevOps"]

@app.get("/favorite")
def get_favorite(number: int = 0):
    if number >= len(courses):
        course = "no valid course was chosen"
    else:
        course = courses[number]
    return {"favorite course": course}
1
2
3
4
5
6
7
8
9
10
11
12
13

As a result of the addition of a default value, the parameter number is now optional. So, going to the URL:

127.0.0.1:8000/favorite

would be the same as going to:

127.0.0.1:8000/favorite?number=0

But if you go to, for example:

127.0.0.1:8000/favorite?number=2

The parameter value in your function will be:

  • number=2: because you set it in the URL

# Multiple parameters

Until this point, we only worked with one parameter each time. But in reality you can and probably will have many parameters. Let's add a second parameter to our previous example.








 






from fastapi import FastAPI

app = FastAPI()

courses = ["Programming Essentials", "Webdesign Essentials", "Data Essentials", "Full Stack Essentials", "Cloud & DevOps"]

@app.get("/favorite")
def get_favorite(number: int = 0, rating: float = 5):
    if number >= len(courses):
        course = "no valid course was chosen"
    else:
        course = courses[number]
    return {"favorite course": course, "rating": rating}
1
2
3
4
5
6
7
8
9
10
11
12
13

You would send a request to this endpoint in the following way, provided that you want to use both query parameters:

127.0.0.1:8000/favorite?number=2&rating=9.5

Notice how the parameters are seperated in the URL. You use the ampersand character (&) to seperate two parameters.

The response of this request would be this:

{
    "favorite course": "Data Essentials",
    "rating": 9.5
}
1
2
3
4

# Optional parameters

In the previous examples we have seen that you can make a parameter optional by giving it a default value. So not defining a default value means the parameter is required. In case you want your parameter to be optional, but you do not have a pre-determined value to assign to it, you can use the default value None (which would correspond to null in most programming languages).

Let's look at an example:








 






from fastapi import FastAPI

app = FastAPI()

courses = ["Programming Essentials", "Webdesign Essentials", "Data Essentials", "Full Stack Essentials", "Cloud & DevOps"]

@app.get("/favorite")
def get_favorite(number: int = 0, rating: float = 5, comment: str = None):
    if number >= len(courses):
        course = "no valid course was chosen"
    else:
        course = courses[number]
    return {"favorite course": course, "rating": rating, "comment": comment}
1
2
3
4
5
6
7
8
9
10
11
12
13

In this case, the query parameter comment will be optional, and as such will be None (= null/empty/nothing) by default.

So, by sending a request to the API in the following manner, you would use the comment parameter:

127.0.0.1:8000/favorite?number=2&rating=9.5&comment=cool

The resulting response is as follows:

{
    "favorite course": "Data Essentials",
    "rating": 9.5,
    "comment": "cool"
}
1
2
3
4
5

Leaving out the comment parameter in the URL as such:

127.0.0.1:8000/favorite?number=2&rating=9.5

... will now give the following result:

{
    "favorite course": "Data Essentials",
    "rating": 9.5,
    "comment": null
}
1
2
3
4
5

Spaces in the URL

In case you need multiple words in your comment, seperated by spaces: replace the spaces in your URL by plus signs (+).

For example:

127.0.0.1:8000/favorite?number=2&rating=9.5&comment=Amazing+teachers!

This will give the following result:

{
    "favorite course": "Data Essentials",
    "rating": 9.5,
    "comment": "Amazing teachers!"
}
1
2
3
4
5

# Other type conversions

You can also declare bool types, and they will be converted as well:








 






from fastapi import FastAPI

app = FastAPI()

courses = ["Programming Essentials", "Webdesign Essentials", "Data Essentials", "Full Stack Essentials", "Cloud & DevOps"]

@app.get("/favorite")
def get_favorite(number: int = 0, rating: float = 5, comment: str = None, passed: bool = False):
    if number >= len(courses):
        course = "no valid course was chosen"
    else:
        course = courses[number]
    return {"favorite course": course, "rating": rating, "comment": comment, "passed": passed}
1
2
3
4
5
6
7
8
9
10
11
12
13

In this case, if you go to:

127.0.0.1:8000/favorite?number=2&rating=9.5&passed=1

127.0.0.1:8000/favorite?number=2&rating=9.5&passed=True

127.0.0.1:8000/favorite?number=2&rating=9.5&passed=true

127.0.0.1:8000/favorite?number=2&rating=9.5&passed=on

127.0.0.1:8000/favorite?number=2&rating=9.5&passed=yes

or any other case variation (uppercase, first letter in uppercase, etc.), your function will see the parameter passed with a bool value of True and give the result shown below. Otherwise it will see the parameter as False.

{
    "favorite course": "Data Essentials",
    "rating": 9.5,
    "comment": null,
    "passed": true
}
1
2
3
4
5
6

These are the primitive data types in Python:

Data Type Description
Integer (int) Whole numbers, positive or negative, without decimals.
Floating-point (float) Real numbers, which have a decimal point.
String (str) Sequence of characters (text).
Boolean (bool) Represents truth values, True or False.

# Interactive API documentation (docs)

Now go to http://127.0.0.1:8000/docs (opens new window).

You will see the automatic interactive API documentation (provided by Swagger UI (opens new window)):

Swagger UI

Notice that in our /favorite endpoint, the query parameters are now visible, including their default value.

# Exercise 1: Random number generator

Create a new Python project. Create a new file called main.py within that project. This API will contain several endpoints which give functionality for generating random numbers.

Create a first GET request with the following endpoint:

/percentage/fixedlimit
1

The result of this GET request will be a JSON object that gives a random number between 0 and 100 (included). For example:

{
    "percentage": 95
}
1
2
3

Create a second endpoint for a GET request. For this request, the upper and lower limit of the random number will no longer be hard coded. Instead, give the user or application that is addressing your API the opportunity to make use of his/her own limits. For example:

/percentage/limits?lower_limit=10&upper_limit=50
1

The endpoint shown above will give a random number between 10 and 50 (included). The returned JSON should have the same structure as in the first GET request.

Make sure that the upper limit is greater than the lower limit. If that is not the case, return the following error message:

{
    "error": "The upper limit must be greater than the lower limit!",
    "lower limit": 57,
    "upper limit": 50
}
1
2
3
4
5

Please notice that the upper and lower limit are passed in the result as well. So for the example above, the following url would be used:

http://127.0.0.1:8000/percentage/limits?lower_limit=57&upper_limit=50 (opens new window)

Create a third endpoint in which you can not only pass the limits, but also the amount of random numbers that should be returned.

In the example that follows, the lower limit is 20, the upper limit is 50 and the amount of random numbers to be generated is 3.

/percentage/multiplereadings?lower_limit=20&upper_limit=50&amount=3
1

The result could be as follows:

{
    "percentages": [
        33,
        20,
        46
    ]
}
1
2
3
4
5
6
7

Make sure that the amount of numbers to generate (third parameter) is an integer. If not, the default data conversion error of FastAPI should be thrown:

{
    "detail": [
        {
            "type": "int_parsing",
            "loc": [
                "query",
                "amount"
            ],
            "msg": "Input should be a valid integer, unable to parse string as an integer",
            "input": "three",
            "url": "https://errors.pydantic.dev/2.5/v/int_parsing"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Make sure that the upper limit is greater than the lower limit, similar to the check you performed in the previous endpoint.

Make sure that the amount of random numbers is greater than 0. Otherwise, return the following error:

{
    "error": "The amount must be greater than 0!",
    "amount": -3
}
1
2
3
4

TIP

Make your code as efficient as possible: first check for errors and only perform other logic after all possible errors have been checked.

# Exercise 2: Zoo

Create an API that will help a zoo by suggesting new animals to add to their existing collection of animals.

Create a new Python project. Create a new file called main.py within that project and first of all add the following list with animals your API can suggest:

all_animals = ["badger", "lion", "wolf", "porcupine", "cheetah", "giraffe", "panda", "gorilla", "tiger", "koala", "jaguar", "elephant", "blue-and-yellow macaw", "hornbill", "rhinoceros", "kangaroo", "ostridge", "bison", "wallaby"]
1

Create the endpoint /zoo and make sure it will send back a number of randomly chosen animals. Of these animals, their sex will also be randomly chosen.

The application or user addressing this endpoint might choose how many animals should be returned/suggested by the API. If that number is not determined, the API should provide exactly 5 animals. In case the endpoint is addressed and more or less than 5 animals should be returned, it will be called in the following manner:

http://127.0.0.1:8000/zoo?number=2 (opens new window)

The following JSON will be returned in that case:

{
    "animals": [
        {
            "number": 1,
            "species": "hornbill",
            "sex": "male"
        },
        {
            "number": 2,
            "species": "bison",
            "sex": "male"
        }
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Note

The value corresponding with the number key in each animal object will be increased by 1 with every animal shown. So in case you have 10 animal objects, the value for the last animal will be 10.

Pay attention

Please beware that your result will have different animals and sexes than the example above as the animals and sexes are randomly chosen each time you address the API endpoint. Some animals might even appear multiple times at this stage. The sex of these animals can either be male or female.

Make sure that the number parameter is larger than 0. If not, show an error in the following way:

{
    "error": "A number above 0 must be given. Provided number: -3"
}
1
2
3

Let's now build in some more functionality and error handling. First of all, make sure you can't ask for more animals than the number of animals available in the original list with animals. When you do ask for more animals, show an error as shown below.

An example would be sending a request using the following URL (so with 103 as value for number):

http://127.0.0.1:8000/zoo?number=103 (opens new window)

{
    "error": "You asked for more animals than available. Provided number: 103 - Available number: 19"
}
1
2
3

The available number in the error message above should correspond to the number of animals in the all_animals list.

Next, make sure only unique animals are returned by the endpoint. (So: a maximum of 1 badger, 1 lion, 1 wolf, etc.)

TIP

To achieve the functionality asked for above, you need to make a copy of the original list in your path operation function. Making a pure copy would keep a reference to the original list, we however do not want that. Instead, make use of the copy() function for lists in Python.

More info and examples on the copy() function can be found here:

Last Updated: 3/6/2024, 7:28:07 PM