5

How can I include one APIRouter inside another AND specify that some path param is a required prefix?

For context: Let's say I have the concept of an organization and a user. A user only belongs to one organization. My web app could be structured as follows:

├── web_app
    ├── endpoints
        ├── __init__.py # produces main_router by including other routers inside each other
        ├── organization.py # contains endpoints relevant to the organization
        ├── user.py # contains endpoints relevant to the user
    └── main.py # includes main_router in app

Let's assume I want to achieve basic CRUD functionality for organizations and users. My endpoints could look something like this:

For orgs:

GET /api/latest/org/{org_id}
POST /api/latest/org/{org_id}
PUT /api/latest/org/{org_id}
DELETE /api/latest/org/{org_id}

For users:

GET /api/latest/org/{org_id}/users/{user_id}
POST /api/latest/org/{org_id}/users/{user_id}
PUT /api/latest/org/{org_id}/users/{user_id}
DELETE /api/latest/org/{org_id}/users/{user_id}

Since users are nested under orgs, within user.py, I could write all of my endpoints like this:

user_router = APIRouter()
@user_router.get("/org/{org_id}/users/{user_id}")
async def get_user(org_id, user_id):
    ...

But that gets gross really quick. The user_router is completely disjointed from the org_router even though one should be nested inside the other. If I make a change to the org router, I now need to change every single user router endpoint. God forbid I have something nested under users....

So as per my question, I was hoping something like this would work:

# user.py
user_router = APIRouter(prefix="/org/{org_id}/users")
@user_router.get("/{user_id}")
async def get_user(org_id, user_id):
# __init__.py
org_router.include_router(user_router)
main_router = APIRouter(prefix="/api/latest/")
main_router.include_router(org_router)

but that gives me the following error: AssertionError: Path params must be of one of the supported types. I don't get the error if I remove {org_id} from the prefix, so I know APIRouter(prefix="/org/{org_id}/users") is the problem.

This is the only documentation we get from FastAPI on the matter: https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-in-another

Is what I'm looking for even possible? This seems like an extremely common situation so I'm curious what other folks do.

1 Answer 1

8

I am not sure why you are getting this error, but the thing that you are requesting (using path parameters in APIRoute prefix) is absolutely possible. When you include the APIRoute to the app instance, it will evaluate all routes and appends them from your dedicated APIRoute to the main APIRoute. The latter is always present in the app = FastAPI() app object. The path parameters itself are resolved upon a request.

Below is a fully working example, which will run as is:

from fastapi import APIRouter, FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"hello": "world"}


router = APIRouter(prefix="/org/{org_id}")


@router.get("/users/{user_id}")
def get_user(org_id: int, user_id: int):
    return {"org": org_id, "user:": user_id}


app.include_router(router)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

We can check the outcome with curl:

% curl localhost:8000/org/1/users/2
{"org":1,"user:":2}%
Sign up to request clarification or add additional context in comments.

1 Comment

Thank you for the reply! You're right this is totally doable. The AssertionError I was getting was because the path params were actually doing their job. I had some endpoints that were not yet expecting the new param types.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.