Browse Source

fix: support mounting StaticFiles to APIRouter, raise error for other apps

- Override APIRouter.mount() to only allow StaticFiles; raise FastAPIError
  for any other ASGI app with a clear message directing users to mount
  sub-applications on the root FastAPI instance instead
- Add APIRouter._mount() as internal bypass used by include_router()
  and FastAPI.mount()
- Override FastAPI.mount() to call self.router._mount() directly,
  bypassing the StaticFiles-only restriction on APIRouter
- Propagate StaticFiles mounts in include_router() via _mount()

Fixes #10180
pull/14924/head
kimchikingdom 5 months ago
parent
commit
fbcdc4ad28
  1. 3
      fastapi/applications.py
  2. 22
      fastapi/routing.py
  3. 33
      tests/test_router_mount.py

3
fastapi/applications.py

@ -1133,6 +1133,9 @@ class FastAPI(Starlette):
scope["root_path"] = self.root_path
await super().__call__(scope, receive, send)
def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
self.router._mount(path, app=app, name=name)
def add_api_route(
self,
path: str,

22
fastapi/routing.py

@ -994,6 +994,26 @@ class APIRouter(routing.Router):
self.default_response_class = default_response_class
self.generate_unique_id_function = generate_unique_id_function
def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
"""
Mount a `StaticFiles` instance at the given path.
Raises `FastAPIError` if `app` is not an instance of `StaticFiles`,
since mounting arbitrary ASGI sub-applications under an `APIRouter`
is not supported. Sub-applications should be mounted directly on
the root `FastAPI` instance instead.
"""
if not isinstance(app, StaticFiles):
raise FastAPIError(
"APIRouter does not support mounting ASGI applications other than "
"StaticFiles. Mount sub-applications directly on the root FastAPI "
"instance instead."
)
self._mount(path=path, app=app, name=name)
def _mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
super().mount(path=path, app=app, name=name)
def route(
self,
path: str,
@ -1492,7 +1512,7 @@ class APIRouter(routing.Router):
)
elif isinstance(route, routing.Mount):
if isinstance(route.app, StaticFiles):
self.mount(prefix + router.prefix + route.path, route.app, name=route.name)
self._mount(prefix + router.prefix + route.path, route.app, name=route.name)
for handler in router.on_startup:
self.add_event_handler("startup", handler)
for handler in router.on_shutdown:

33
tests/test_router_mount.py

@ -3,6 +3,7 @@ from pathlib import Path
import pytest
from fastapi import APIRouter, FastAPI
from fastapi.exceptions import FastAPIError
from fastapi.staticfiles import StaticFiles
from fastapi.testclient import TestClient
@ -68,27 +69,13 @@ def test_mount_staticfiles_with_include_router_prefix(tmp_path: Path) -> None:
assert r.text == "combined prefix"
def test_mount_non_staticfiles_app_is_ignored() -> None:
"""Mounting a non-StaticFiles ASGI app on APIRouter should be silently ignored
(not propagated to the main app) to preserve existing behavior."""
app = FastAPI()
api_router = APIRouter(prefix="/api")
subapi = FastAPI()
@subapi.get("/sub")
def read_sub() -> dict:
return {"message": "sub"}
api_router.mount("/subapi", subapi)
app.include_router(api_router)
client = TestClient(app)
# Non-StaticFiles sub-app mount is not propagated — returns 404
r = client.get("/api/subapi/sub")
assert r.status_code == 404
def test_mount_non_staticfiles_app_raises_error() -> None:
"""Mounting a non-StaticFiles ASGI app on APIRouter should raise FastAPIError."""
router = APIRouter()
sub_app = FastAPI()
# Regular routes are unaffected
# (no routes defined here, but include_router itself should not crash)
assert app is not None
with pytest.raises(
FastAPIError,
match="APIRouter does not support mounting ASGI applications other than StaticFiles",
):
router.mount("/sub", sub_app)

Loading…
Cancel
Save