Browse Source
Added support for mounting sub-applications on APIRouter with proper
prefix handling:
1. Override mount() in APIRouter to automatically apply the router's
prefix to the mount path
2. Handle Mount routes in include_router() to apply the include prefix
This allows patterns like:
router = APIRouter(prefix='/api')
router.mount('/subapp', FastAPI())
app.include_router(router)
Which correctly routes requests to /api/subapp/...
Note: Mounts must be added before include_router() is called, as
include_router copies routes at call time. This is consistent with
how other routes work.
Fixes #10180
pull/14793/head
2 changed files with 150 additions and 0 deletions
@ -0,0 +1,133 @@ |
|||
"""Test mounting sub-applications under APIRouter.""" |
|||
|
|||
from fastapi import APIRouter, FastAPI |
|||
from fastapi.testclient import TestClient |
|||
|
|||
|
|||
def test_router_mount_basic(): |
|||
"""Sub-applications mounted on a router should work when included in the app.""" |
|||
app = FastAPI() |
|||
api_router = APIRouter(prefix="/api") |
|||
|
|||
@api_router.get("/main") |
|||
def read_main(): |
|||
return {"message": "Hello from main"} |
|||
|
|||
subapi = FastAPI() |
|||
|
|||
@subapi.get("/sub") |
|||
def read_sub(): |
|||
return {"message": "Hello from sub"} |
|||
|
|||
# Mount BEFORE include_router |
|||
api_router.mount("/subapi", subapi) |
|||
app.include_router(api_router) |
|||
|
|||
client = TestClient(app) |
|||
|
|||
# Main route should work |
|||
response = client.get("/api/main") |
|||
assert response.status_code == 200 |
|||
assert response.json() == {"message": "Hello from main"} |
|||
|
|||
# Sub-application route should also work |
|||
response = client.get("/api/subapi/sub") |
|||
assert response.status_code == 200 |
|||
assert response.json() == {"message": "Hello from sub"} |
|||
|
|||
|
|||
def test_router_mount_with_include_prefix(): |
|||
"""Mount path should combine with both router prefix and include_router prefix.""" |
|||
app = FastAPI() |
|||
api_router = APIRouter(prefix="/v1") |
|||
|
|||
subapi = FastAPI() |
|||
|
|||
@subapi.get("/endpoint") |
|||
def sub_endpoint(): |
|||
return {"version": "1"} |
|||
|
|||
api_router.mount("/mounted", subapi) |
|||
app.include_router(api_router, prefix="/api") |
|||
|
|||
client = TestClient(app) |
|||
|
|||
# Full path: /api + /v1 + /mounted + /endpoint |
|||
response = client.get("/api/v1/mounted/endpoint") |
|||
assert response.status_code == 200 |
|||
assert response.json() == {"version": "1"} |
|||
|
|||
|
|||
def test_router_mount_without_prefix(): |
|||
"""Mount should work on router without prefix.""" |
|||
app = FastAPI() |
|||
api_router = APIRouter() # No prefix |
|||
|
|||
subapi = FastAPI() |
|||
|
|||
@subapi.get("/hello") |
|||
def hello(): |
|||
return {"hello": "world"} |
|||
|
|||
api_router.mount("/sub", subapi) |
|||
app.include_router(api_router) |
|||
|
|||
client = TestClient(app) |
|||
|
|||
response = client.get("/sub/hello") |
|||
assert response.status_code == 200 |
|||
assert response.json() == {"hello": "world"} |
|||
|
|||
|
|||
def test_router_mount_applies_router_prefix(): |
|||
"""Mount path should include the router's prefix.""" |
|||
router = APIRouter(prefix="/api") |
|||
|
|||
subapi = FastAPI() |
|||
|
|||
@subapi.get("/test") |
|||
def test_route(): |
|||
return {"test": True} |
|||
|
|||
router.mount("/mounted", subapi) |
|||
|
|||
# Check that the mount path includes the router prefix |
|||
mount_route = None |
|||
for route in router.routes: |
|||
if hasattr(route, "path") and "mounted" in route.path: |
|||
mount_route = route |
|||
break |
|||
|
|||
assert mount_route is not None |
|||
assert mount_route.path == "/api/mounted" |
|||
|
|||
|
|||
def test_router_mount_multiple_subapps(): |
|||
"""Multiple sub-applications can be mounted on the same router.""" |
|||
app = FastAPI() |
|||
api_router = APIRouter(prefix="/api") |
|||
|
|||
subapi1 = FastAPI() |
|||
subapi2 = FastAPI() |
|||
|
|||
@subapi1.get("/one") |
|||
def route_one(): |
|||
return {"app": 1} |
|||
|
|||
@subapi2.get("/two") |
|||
def route_two(): |
|||
return {"app": 2} |
|||
|
|||
api_router.mount("/first", subapi1) |
|||
api_router.mount("/second", subapi2) |
|||
app.include_router(api_router) |
|||
|
|||
client = TestClient(app) |
|||
|
|||
response1 = client.get("/api/first/one") |
|||
assert response1.status_code == 200 |
|||
assert response1.json() == {"app": 1} |
|||
|
|||
response2 = client.get("/api/second/two") |
|||
assert response2.status_code == 200 |
|||
assert response2.json() == {"app": 2} |
|||
Loading…
Reference in new issue