How to provide only one of multiple optional parameters in FastAPI?
Asked Answered
A

2

2

The API should serve an endpoint where a user can either provide a guid, a path or a code. This is what the url scheme should look like:

api/locations?code={str}&guid={str}&path={str}

The current code is as follows:

@router.get("/api/locations")
def get_functional_locations(
    guid: Optional[str] = None,
    code: Optional[str] = None,
    path: Optional[str] = None,
) -> Union[List[Locations], PlainTextResponse]:
   if guid:
        return ...

    if path:
        return ...

    if code:
        return ...

    return PlainTextResponse(status_code=status.HTTP_400_BAD_REQUEST)

Is there another way to provide this multiple optional parameters and have an XOR that only allows the user to fill in exactly one parameter?

Alaniz answered 18/4, 2023 at 11:51 Comment(0)
T
4

Firstly, I would ask, if you have three parameters that are mutually exclusive and all are ways to get the same information, shouldn't that be three different endpoints, e.g. api/locations/guid/{guid}, api/locations/code/{code} and api/locations/path/{path}? It's usually preferable to have several smaller functions that do one thing.

If you however do want this, you can always write your own code for it, e.g. using a class as a dependency, here together with a pydantic root validator

from fastapi import Depends, HttpException
from pydantic import BaseModel, root_validator

class Params(BaseModel):
    guid: str | None = None
    code: str | None = None
    path: str | None = None

    @root_validator
    def validate(cls, values):
        if len([val for val in values.values() if val is not None]) != 1:
            raise HTTPException(400, "Exactly one of guid, code, and path must be provided")
        return values


@app.get("/api/locations")
def foo(params: Params = Depends()):
    ...
Truck answered 18/4, 2023 at 12:38 Comment(0)
B
1

You could use a dependency, which is a function that can take all the same parameters that an endpoint can take. Inside the dependency function, you could perform the required checks to ensure that the user has provided only one of the optional parameters. If so, return a dictionary back to the endpoint, including the parameters and their values, which can help you find which parameter (and its value) was used by the client. Otherwise, if values for more than one parameters were provided, you could raise an HTTPException, informing the user for the restrictions applied to that endpoint.

Please note that the example below uses the Optional keyword from the typing module (as shown in the example provided in your question) to declare optional parameters. However, in Python 3.10+, one could also use, for instance, guid: str | None = None. In either case, the most important part to make a parameter optional is the part = None. Please have a look at this answer and this answer to find more details and all the available options to make parameters optional in FastAPI.

Working Example

from fastapi import FastAPI, Depends, HTTPException
from typing import Optional

app = FastAPI()


def params(
    guid: Optional[str] = None,
    code: Optional[str] = None,
    path: Optional[str] = None,
):
    if sum(i is not None for i in [guid, code, path]) != 1:
        raise HTTPException(400, 'Please provide only one of either guid, code or path')
    else:
        return {'guid': guid, 'code': code, 'path': path}


@app.get('/')
def main(d: dict = Depends(params)):
    for k, v in d.items():
        if v:
            return {k: v}
Bye answered 19/4, 2023 at 13:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.