pythonfastapiurl-routingpydanticstarlette

How to dynamically create FastAPI routes/handlers for a list of Pydantic models?


I have a problem using FastAPI, trying to dynamically define routes. It seems that the final route/handler defined overrides all the previous ones (see image below)

Situation: I have a lot of Pydantic models as a list (imported from models.py: simplified in example), and would like to create a GET endpoint for each model.

Something like this (my app is more complex — there is a reason it is structured as it is — but I've simplified it down to this, in order to find the problem, which still occurs):

# models.py

class Thing(BaseNode):
    value: str

class Other(BaseNode):
    value: str

model_list = [Thing, Other]



# app.py
from models import model_list

app = FastApi()


# Iterate over the models list and define a get function
for model in models_list:

    @app.get(f"/{model.__name__.lower()}")
    def get() -> str:
         print(model.__name__)
         return f"Getting {model.__name__)

Calling GET Thing returns the response for GET Other

I'm obviously doing something wrong (or FastAPI references the handler function in some weird way... by module/function name(?) so the get() function is being re-defined for each iteration?) Any ideas, or better way to structure this? (I can't manually write out a new function for each model!)

Thanks!

UPDATE: Update:

If I define the get() function inside another function, it works:

for model in models_list:
     def get(model):
         def _get():
             print("Getting", model)
                
             return f"Getting {model.__name__}"
         return _get

     app.get(.get(f"/{model.__name__.lower()}", name=f"{model.__name__}.View")(get(model))


Solution

  • Most likely the issue lies in the fact that you are using the same model reference for every endpoint created, and hence, it is always assigned the last value given in the for loop.

    You should rather use a helper function that returns an inner function, as demonstrated in Option 2 of this answer.

    Example

    from fastapi import FastAPI, APIRouter, Request
    from pydantic import BaseModel
    
    
    class One(BaseModel):
        value: str
    
    
    class Two(BaseModel):
        value: str
    
    
    app = FastAPI()
    models = [One, Two]
    
    
    def create_endpoint(m_name: str):
        async def endpoint(request: Request):
            print(request.url)
            return f"You called {m_name}"
        return endpoint
    
    
    for m in models:    
        app.add_api_route(f"/{m.__name__.lower()}", create_endpoint(m.__name__), methods=["GET"])