3

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 .../?

masoud
  • 55,379
  • 16
  • 141
  • 208
  • Have you seen https://stackoverflow.com/questions/63069190/how-to-capture-arbitrary-paths-at-one-route-in-fastapi and https://fastapi.tiangolo.com/advanced/custom-request-and-route/ ? The first shows how you can use a `path` type to capture the path, while the other one shows how you can use a custom apiroute class if you want to customize the lower level functionality. – MatsLindh Aug 09 '22 at 11:47
  • Please have a look at [this answer](https://stackoverflow.com/a/72815364/17865804). – Chris Aug 09 '22 at 12:55

2 Answers2

2

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

Tom McLean
  • 5,583
  • 1
  • 11
  • 36
  • I am using this right now, thanks. However I think FastAPI should support these kind of functions while it is supporting dynamic routing via add_api_route. I edited my question to describe the problem. – masoud Aug 13 '22 at 08:13
1

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"}
Chris
  • 18,724
  • 6
  • 46
  • 80
  • Thanks for the answer, your point about `path` inside route is very useful, however I am trying to extract parameter within url, but your code is extracting query parameters. – masoud Aug 09 '22 at 19:13