Werner Raath
Werner Raath

Reputation: 1482

FastAPI variable query parameters

I am writing a Fast API server that accepts requests, checks if users are authorized and then redirects them to another URL if successful.

I need to carry over URL parameters, e.g. http://localhost:80/data/?param1=val1&param2=val2 should redirect to http://some.other.api/?param1=val1&param2=val2, thus keeping previously allotted parameters.

The parameters are not controlled by me and could change at any moment.

How can I achieve this?

Code:

from fastapi import FastAPI
from starlette.responses import RedirectResponse

app = FastAPI()

@app.get("/data/")
async def api_data():
    params = '' # I need this value
    url = f'http://some.other.api/{params}'
    response = RedirectResponse(url=url)
    return response

Upvotes: 30

Views: 75565

Answers (6)

fjemi
fjemi

Reputation: 17

For an arbitrary number of query params, you could use two params that are both lists, names and values, which contain the names and values of the params. Retrieve the params from the fastapi request object as a dictionary and split the values of the dictionary into lists. Then use the index of a param's name in the names list to access it's value in the values list.

The url could look like this: http://localhost:80/data/?names=name1,name2,name3&values=value1,value2,value3.

params = request.query_params._dict
# {'names': 'names2,names1,names3', 'values': 'value1,value2,value3'}
print(params)

for key, value in params.items():
  value = value.split(',')
  params[key] = value

# {'names': ['names2', 'names1', 'names3'], 'values': ['value1', 'value2', 'value3']}
print(params)


index = params['names'].index('names2')
value = params['values'][index]
# 'value3'
print(value)

Upvotes: -1

Victor Bazterra
Victor Bazterra

Reputation: 179

This is a code I derived from @Hajar Razip using a more pydantic like approach:

from pydantic import (
    BaseModel,
)
from typing import (
    Dict,
    List,
    Optional,
)

from fastapi import (
    Depends,
    FastAPI,
    Query,
    Request,
)


class QueryParameters(BaseModel):
    """Model for query parameter."""
    fixId: Optional[str]
    fixStr: Optional[str]
    fixList: Optional[List[str]]
    fixBool: Optional[bool]
    dynFields: Dict

    _aliases: Dict[str,str] = {"id": "fixId"}

    @classmethod
    def parser(
        cls, 
        request: Request,
        fixId: Optional[str] = Query(None, alias="id"),
        fixStr: Optional[str] = Query(None),
        fixList: Optional[List[str]] = Query(None),
        fixBool: bool = Query(True),
    ) -> Dict:
        """Parse query string parameters."""
        dynFields = {}
        reserved_keys = cls.__fields__
        query_keys = request.query_params
        for key in query_keys:
            key = cls._aliases.get(key, key) 
            if key in reserved_keys:
                continue
            dynFields[key] = request.query_params[key]
        
        return {
            "fixId": fixId,
            "fixStr": fixStr,
            "fixList": fixList,
            "fixBool": fixBool,
            "dynFields": dynFields
        }


app = FastAPI()


@app.get("/msg")
def get_msg(
    parameters: QueryParameters = Depends(
            QueryParameters.parser,
    ),
) -> None:
    return parameters

The output documentation is then

enter image description here

Here it is the result of calling GET /msg

> curl -s -X 'GET' 'http://127.0.0.1:8000/msg?id=Victor&fixStr=hi&fixList=eggs&fixList=milk&fixList=oranges&fixBool=true' -H 'accept: application/json' | python3 -m json.tool
{
    "fixId": "Victor",
    "fixStr": "hi",
    "fixList": [
        "eggs",
        "milk",
        "oranges"
    ],
    "fixBool": true,
    "dynFields": {}
}

Here it is the GET /msg call using dynamic fields

>  curl -s -X 'GET' 'http://127.0.0.1:8000/msg?id=Victor&fixStr=hi&fixList=eggs&fixList=milk&fixList=oranges&fixBool=true&key1=value1&key2=value2' -H 'accept: application/json' | python3 -m json.tool
{
    "fixId": "Victor",
    "fixStr": "hi",
    "fixList": [
        "eggs",
        "milk",
        "oranges"
    ],
    "fixBool": true,
    "dynFields": {
        "key1": "value1",
        "key2": "value2"
    }
}

Upvotes: 3

Hajar Razip
Hajar Razip

Reputation: 593

I use a combination of Depends, BaseModel and the Request object itself.

Here's an example for a HTTP request like localhost:5000/api?requiredParam1=value1&optionalParam2=value2&dynamicParam1=value3&dynamicParam2=value4

# imports
from typing import Union
from pydantic import BaseModel
from fastapi import Depends, Request

# the base model
class QueryParams(BaseModel):
    required: str
    optional: Union[None, str] = None
    dynamic: dict

# dependency
async def query_params(
    request: Request, requiredParam1: str, optionalParam1: Union[None, str] = None
    ):
    # process the request here
    dynamicParams = {}
    for k in request.query_params.keys():
        if 'dynamicParam' not in k:
            continue
        dynamicParams[k] = request.query_params[k]

    # also maybe do some other things on the arguments
    # ...

    return {
        'required': requiredParam1,
        'optional': optionalParam1,
        'dynamic': dynamicParams
    }

# the endpoint
@app.get("api/")
async def hello(params: QueryParams = Depends(query_params)):

    # Maybe do domething with params here,
    # Use it as you would any BaseModel object
    # ...

    return params

Refer the Starlette documentation on how to use the request object: https://www.starlette.io/requests/

Note that you can put query_params in a different module, and need not add any more code to explicitly pass the Request object. FastAPI already does that when you make a call to the endpoint :)

Upvotes: 3

miksus
miksus

Reputation: 3417

If the query parameters are known when starting the API but you still wish to have them dynamically set:

from fastapi import FastAPI, Depends
from pydantic import create_model

app = FastAPI()

# Put your query arguments in this dict
query_params = {"name": (str, "me")}

query_model = create_model("Query", **query_params) # This is subclass of pydantic BaseModel

# Create a route
@app.get("/items")
async def get_items(params: query_model = Depends()):
    params_as_dict = params.dict()
    ...

This has the benefit that you see the parameters in the automatic documentation:

Swagger UI

But you are still able to define them dynamically (when starting the API).

Note: if your model has dicts, lists or other BaseModels as field types, the request body pops up. GET should not have body content so you might want to avoid those types.

See more about dynamic model creation from Pydantic documentation.

Upvotes: 18

Werner Raath
Werner Raath

Reputation: 1482

In the docs they talk about using the Request directly, which then lead me to this:

from fastapi import FastAPI, Request
from starlette.responses import RedirectResponse

app = FastAPI()

@app.get("/data/")
async def api_data(request: Request):
    params = request.query_params
    url = f'http://some.other.api/?{params}'
    response = RedirectResponse(url=url)
    return response

Upvotes: 38

qaiser
qaiser

Reputation: 2868

As mention in docs of FastAPI https://fastapi.tiangolo.com/tutorial/query-params-str-validations/.

 @app.get("/")
 def read_root(param1: Optional[str] = None, param2: Optional[str] = None):
     url = f'http://some.other.api/{param1}/{param2}'
     return {'url': str(url)}

output

enter image description here

enter image description here

Upvotes: 6

Related Questions