Add route to FastAPI with custom path parameters

Question:

I am trying to add routes from a file and I don’t know the actual arguments beforehand so I need to have a general function that handles arguments via **kwargs.

To add routes I am using add_api_route as below:

from fastapi import APIRouter

my_router = APIRouter()

def foo(xyz):
    return {"Result": xyz}

my_router.add_api_route('/foo/{xyz}', endpoint=foo)

Above works fine.

However enrty path parameters are not fixed and I need to read them from a file, to achieve this, I am trying something like this:

from fastapi import APIRouter

my_router = APIRouter()

def foo(**kwargs):
    return {"Result": kwargs['xyz']}

read_from_file = '/foo/{xyz}' # Assume this is read from a file

my_router.add_api_route(read_from_file, endpoint=foo)

But it throws this error:

{"detail":[{"loc":["query","kwargs"],"msg":"field required","type":"value_error.missing"}]}

FastAPI tries to find actual argument xyz in foo signature which is not there.

Is there any way in FastAPI to achieve this? Or even any solution to accept a path like /foo/... whatever .../?

Asked By: masoud

||

Answers:

This will generate a function with a new signature (I assume every parameter is a string):

from fastapi import APIRouter
import re
import inspect

my_router = APIRouter()


def generate_function_signature(route_path: str):
    args = {arg: str for arg in re.findall(r'{(.*?)}', route_path)}
    
    def new_fn(**kwargs):
        return {"Result": kwargs['xyz']}

    params = [
        inspect.Parameter(
            param,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
            annotation=type_
        ) for param, type_ in args.items()
    ]

    new_fn.__signature__ = inspect.Signature(params)
    new_fn.__annotations__ = args
    return new_fn


read_from_file = '/foo/{xyz}' # Assume this is read from a file

my_router.add_api_route(
    read_from_file,
    endpoint=generate_function_signature(read_from_file)
)

However I am sure there is a better way of doing whatever you are trying to do, but I would need to understand your problem first

Answered By: Tom McLean

As described here, you can use the path convertor, provided by Starlette, to capture arbitrary paths. Example:

from fastapi import APIRouter, FastAPI

app = FastAPI()
my_router = APIRouter()

def foo(rest_of_path: str):
    return {"rest_of_path": rest_of_path}
    
route_path = '/foo/{rest_of_path:path}'
my_router.add_api_route(route_path, endpoint=foo)
app.include_router(my_router)

Input test:

http://127.0.0.1:8000/foo/https://placebear.com/cache/395-205.jpg

Output:

{"rest_of_path":"https://placebear.com/cache/395-205.jpg"}
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.