mirror of https://github.com/tiangolo/fastapi.git
Merge b6412448bc into 0127069d47
This commit is contained in:
commit
9b181c076a
|
|
@ -1314,6 +1314,20 @@ class APIRouter(routing.Router):
|
||||||
self.generate_unique_id_function = generate_unique_id_function
|
self.generate_unique_id_function = generate_unique_id_function
|
||||||
self.strict_content_type = strict_content_type
|
self.strict_content_type = strict_content_type
|
||||||
|
|
||||||
|
def mount(
|
||||||
|
self,
|
||||||
|
path: str,
|
||||||
|
app: ASGIApp,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Mount a sub-application or ASGI app at the given path.
|
||||||
|
|
||||||
|
The router's prefix is automatically applied to the mount path.
|
||||||
|
"""
|
||||||
|
# Apply the router's prefix to the mount path
|
||||||
|
super().mount(self.prefix + path, app, name=name)
|
||||||
|
|
||||||
def route(
|
def route(
|
||||||
self,
|
self,
|
||||||
path: str,
|
path: str,
|
||||||
|
|
@ -1819,6 +1833,9 @@ class APIRouter(routing.Router):
|
||||||
self.add_websocket_route(
|
self.add_websocket_route(
|
||||||
prefix + route.path, route.endpoint, name=route.name
|
prefix + route.path, route.endpoint, name=route.name
|
||||||
)
|
)
|
||||||
|
elif isinstance(route, Mount):
|
||||||
|
# Handle mounted sub-applications by re-mounting with the prefix
|
||||||
|
self.mount(prefix + route.path, route.app, name=route.name)
|
||||||
for handler in router.on_startup:
|
for handler in router.on_startup:
|
||||||
self.add_event_handler("startup", handler)
|
self.add_event_handler("startup", handler)
|
||||||
for handler in router.on_shutdown:
|
for handler in router.on_shutdown:
|
||||||
|
|
|
||||||
|
|
@ -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