How to implement greater than or less than query parameters for FastAPI

Question:

I’d like to build an endpoint that looks like /cities?population__gt=100000 to get all cities with a population greater than 100,000.

What’s the best way to do this in FastAPI? I’d prefer not to have to enumerate all the possible operators for a given field, likeso:

@app.get("/cities")
def get_city(
    population: int = None,
    population_gt: int = None,
    population_gte: int = None,
    population_lt: int = None,
    population_lte: int = None):
    return ...
Asked By: jack

||

Answers:

One way to implement this is to pass the operator (i.e., gt, lt, etc), and have a separate function for each operation, which you can call if an operator is received. Example below. You can adjust/add functions as required.

def gt(population):
    return f"operator: gt, population: {population}"
 
def gte(population):
    return f"operator: gte, population: {population}"
 
def lt(population):
    return f"operator: lt, population: {population}"

def lte(population):
    return f"operator: lte, population: {population}"
   
operators = {"gt": gt, "gte": gte, "lt": lt, "lte": lte}
   
@app.get("/cities")
def get_city(population: int = None, operator: str = None):
    if operator in operators:
        result = operators[operator](population)
        return result
    else:
        return "Operator Not Found!"

Update

Since, as you have just said, this is needed for more than one parameters, then I would suggest using an approach similar to this, where you could pass the operator within the value of each key and extract it later on server side, e.g., /cities?population=~gt~100000&population=~lt~500000&size=~eq~100000. You can have operators similar to the link above, i.e., "Equals": ~eq~, "Greater than": ~gt~, "Greater than equals": ~gteq~, and so on.

To receive multiple values for a query parameter, as for the population in the example URL given above, to perform operations such as where population is greater than <number> and less than <number>, you can declare a query parameter with a type of List and explicitly use Query, as described here and explained in the documentation.

The below example gives you the idea of the logic behind this approach using the population parameter, as well as shows how to extract the operator and the actual value on server side. It is then up to you to define the rest of the parameters you need, as well as how to handle these data; for example, using a similar approach to the one above (having a function for each operation), or constructing the query you are about to pass to your database as you loop through the data.

import re
pattern = "~(.*?)~"
 
@app.get("/cities")
def get_city(population: List[str] = Query(None)):
    pop_qs = {}
    
    for p in population:
        try:
            pop_opr = re.search(pattern, p).group(1)
            pop_no_str = re.sub(r"^~.*?~", "", p)
        except:
            return "Invalid data received"
        try:
            pop_no = int(pop_no_str)
            pop_qs.update({pop_opr : pop_no})
        except ValueError:
            return f"Cannot convert '{pop_no_str}' to Integer."
            
    return pop_qs
Answered By: Chris
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.