Reputation: 115
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 ...
Upvotes: 1
Views: 4787
Reputation: 34045
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!"
Since you mentioned that this needs to be done for more than one parameters, I would then 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, for example:
/cities?population=~gt~100000&population=~lt~500000&size=~eq~100000
You could 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, such as the population
in the example URL given above, in order to perform operations such as where population is greater than <number> and less than <number>
, you could declare a query parameter with a type of List
and explicitly use Query
, as described here and explained in the documentation.
The below example provides 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 needed, 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
Upvotes: 2