diff --git a/fastapi/routing.py b/fastapi/routing.py index 4eec6d1c7d..32c5cf1043 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -2004,13 +2004,16 @@ class _FrontendStaticFiles(StaticFiles): return self.file_response(full_path, stat_result, scope) if self.fallback == "404.html" or ( - self.fallback == "auto" and self._fallback_file_exists("404.html") + self.fallback == "auto" and await self._fallback_file_exists("404.html") ): return await self._fallback_response("404.html", scope, status_code=404) if ( self.fallback == "index.html" - or (self.fallback == "auto" and self._fallback_file_exists("index.html")) + or ( + self.fallback == "auto" + and await self._fallback_file_exists("index.html") + ) ) and _is_frontend_navigation_request(scope): return await self._fallback_response("index.html", scope, status_code=200) @@ -2043,8 +2046,8 @@ class _FrontendStaticFiles(StaticFiles): return full_path, stat_result, True return None - def _fallback_file_exists(self, fallback: str) -> bool: - _, stat_result = self.lookup_path(fallback) + async def _fallback_file_exists(self, fallback: str) -> bool: + _, stat_result = await run_in_threadpool(self.lookup_path, fallback) return stat_result is not None and stat.S_ISREG(stat_result.st_mode) async def _fallback_response( diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 0a44df72ed..eb380c8a50 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -1,3 +1,4 @@ +import asyncio import errno import os import runpy @@ -775,11 +776,22 @@ def test_auto_fallback_prefers_404_over_index(tmp_path: Path): assert response.text == "missing" -def test_auto_fallback_uses_index_when_404_is_missing(tmp_path: Path): +def test_auto_fallback_uses_index_when_404_is_missing(monkeypatch, tmp_path: Path): dist = tmp_path / "dist" write_file(dist / "index.html", "app shell") app = FastAPI() app.frontend("/", directory=dist) + frontend_routes = app.router._frontend_routes + assert frontend_routes is not None + static_files = frontend_routes.routes[0].app + original_lookup_path = static_files.lookup_path + + def lookup_path(path: str): + with pytest.raises(RuntimeError, match="no running event loop"): + asyncio.get_running_loop() + return original_lookup_path(path) + + monkeypatch.setattr(static_files, "lookup_path", lookup_path) response = TestClient(app).get("/dashboard", headers={"accept": "text/html"})