Browse Source

Make `APIRouter.mount` work for `StaticFiles`

pull/13944/head
Yurii Motov 5 months ago
parent
commit
c6987ceabb
  1. 5
      fastapi/applications.py
  2. 20
      fastapi/routing.py
  3. 32
      tests/test_apirouter_mount.py

5
fastapi/applications.py

@ -1133,6 +1133,11 @@ class FastAPI(Starlette):
scope["root_path"] = self.root_path scope["root_path"] = self.root_path
await super().__call__(scope, receive, send) 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( def add_api_route(
self, self,
path: str, path: str,

20
fastapi/routing.py

@ -74,6 +74,7 @@ from starlette.routing import (
get_name, get_name,
) )
from starlette.routing import Mount as Mount # noqa from starlette.routing import Mount as Mount # noqa
from starlette.staticfiles import StaticFiles
from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send from starlette.types import AppType, ASGIApp, Lifespan, Receive, Scope, Send
from starlette.websockets import WebSocket from starlette.websockets import WebSocket
from typing_extensions import deprecated from typing_extensions import deprecated
@ -993,6 +994,20 @@ class APIRouter(routing.Router):
self.default_response_class = default_response_class self.default_response_class = default_response_class
self.generate_unique_id_function = generate_unique_id_function 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.
Will raise a FastAPIError exception if app is not an instance of StaticFiles.
"""
if not isinstance(app, StaticFiles):
raise FastAPIError(
"APIRouter does not support mounting ASGI applications other than StaticFiles."
)
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( def route(
self, self,
path: str, path: str,
@ -1489,6 +1504,11 @@ 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 ( # fmt: skip
isinstance(route, routing.Mount) and isinstance(route.app, StaticFiles)
):
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:

32
tests/test_apirouter_mount.py

@ -0,0 +1,32 @@
import pytest
from fastapi import APIRouter, FastAPI
from fastapi.exceptions import FastAPIError
from fastapi.staticfiles import StaticFiles
from fastapi.testclient import TestClient
def test_mount_static_files_to_apirouter(tmp_path):
static_asset = tmp_path / "index.html"
static_asset.write_text("Hello, World!")
router = APIRouter()
router.mount("/static", StaticFiles(directory=tmp_path), name="static")
app = FastAPI()
app.include_router(router)
client = TestClient(app)
response = client.get("/static/index.html")
assert response.status_code == 200
assert response.text == "Hello, World!"
def test_mount_app_to_apirouter_raises():
router = APIRouter()
sub_app = FastAPI()
with pytest.raises(
FastAPIError,
match="APIRouter does not support mounting ASGI applications other than StaticFiles.",
):
router.mount("/sub", sub_app)
Loading…
Cancel
Save