FastAPI swaggerUI shows nested routes twice

Viewed 2586

I have a configuration like this in the routes/__init__.py

## api/routes/__init__.py
router = APIRouter()
router.include_router(models_router, prefix="/models", tags=["models"])
...

And here is the main.py that includes them.

## main.py
from api.routes import router as api_router
def get_app():
    app = FastAPI()
    app.include_router(api_router, prefix = "/api")
    ...

app = get_app() 

Now inside the models router I have two more nested routes like this:

## api/routes/models.py
router.include_router(
    fields_router, 
    prefix="/{model_id}/fields", 
    tags=["fields"],
    dependencies=[Depends(pre_model_validation)]
)
router.include_router(
    model_data_router, 
    prefix="/{model_id}/data", 
    tags=["model_data"],
    dependencies=[Depends(pre_model_validation)]
)

While this works, when I open the localhost and use the generated SwaggerUI docs, it shows something like this

swagger ui docs:

The nested endpoints are also appearing from inside the /models API as well as from their separate /fields and /model_data APIs. How do I isolate the nested routes in a way that they appear as separate API in swagger docs but stay defined inside the /models API?

2 Answers

If I understand correctly you want all endpoints to be under the root path /api/models/ but want the Swagger docs to just show them only once, under the respective 'fields' or 'model_data' tags, while keeping the following under the 'models' tag:

  • Get Models For Site
  • Create Models
  • Update Content Type

If the above is correct, instead of nesting the imports, you might want to split them using the same root path as needed, as follows:

# api/routes/__init__.py
router = APIRouter()

router.include_router(
    models_router, 
    prefix="/models", 
    tags=["models"]
)

router.include_router(
    fields_router, 
    prefix="/models/{model_id}/fields", 
    tags=["fields"]
)

router.include_router(
    models_router, 
    prefix="/models/{model_id}/data", 
    tags=["model_data"]
)

Some workaround can be implemented having the following structure. For demonstration purposes everything is put together:

fields_router = APIRouter()
...

model_data_router = APIRouter()
...

models_router = APIRouter()
...


aggregated_models_router = APIRouter()
aggregated_models_router.include_router(
    fields_router, 
    prefix="/{model_id}/fields", 
    tags=["fields"],
    dependencies=[Depends(pre_model_validation)]
)
aggregated_models_router.include_router(
    model_data_router, 
    prefix="/{model_id}/data", 
    tags=["model_data"],
    dependencies=[Depends(pre_model_validation)]
)
aggregated_models_router.include_router(
    models_router, 
    prefix="", 
    tags=["models"]
)
...

router = APIRouter()
router.include_router(aggregated_models_router, prefix="/models")
...

Without the tags argument in the main router you'll get only fields, model_data and models sections without any duplicates

Related