Browse Source

🔊 Make path errors show absolute path to help with debugging

pull/15800/head
Sebastián Ramírez 1 month ago
parent
commit
c82dc3530d
  1. 19
      fastapi/routing.py
  2. 30
      tests/test_frontend.py

19
fastapi/routing.py

@ -1794,6 +1794,10 @@ def _frontend_path_specificity(path: str) -> int:
return len(path)
def _get_resolved_absolute_path(path: str | os.PathLike[str]) -> str:
return os.path.realpath(os.fspath(path))
class _FrontendStaticFiles(StaticFiles):
def __init__(
self,
@ -1803,6 +1807,11 @@ class _FrontendStaticFiles(StaticFiles):
check_dir: bool = True,
) -> None:
self.fallback = fallback
if check_dir and not os.path.isdir(directory):
raise RuntimeError(
f"Frontend directory {directory!r} does not exist. "
f"Resolved absolute path: {_get_resolved_absolute_path(directory)!r}"
)
super().__init__(
directory=directory,
html=True,
@ -1817,9 +1826,14 @@ class _FrontendStaticFiles(StaticFiles):
if stat_result is None or not stat.S_ISREG(stat_result.st_mode):
raise RuntimeError(
f"Frontend fallback file '{fallback}' does not exist in "
f"directory '{self.directory}'"
f"directory '{self.directory}'. Resolved absolute directory: "
f"'{self._get_resolved_directory()}'"
)
def _get_resolved_directory(self) -> str:
assert self.directory is not None
return _get_resolved_absolute_path(self.directory)
def get_path(self, scope: Scope) -> str:
path = _get_fastapi_scope(scope).get(_FASTAPI_FRONTEND_PATH_KEY, "")
assert isinstance(path, str)
@ -1879,7 +1893,8 @@ class _FrontendStaticFiles(StaticFiles):
if stat_result is None or not stat.S_ISREG(stat_result.st_mode):
raise RuntimeError(
f"Frontend fallback file '{fallback}' does not exist in "
f"directory '{self.directory}'"
f"directory '{self.directory}'. Resolved absolute directory: "
f"'{self._get_resolved_directory()}'"
)
return self.file_response(
full_path, stat_result, scope, status_code=status_code

30
tests/test_frontend.py

@ -692,11 +692,16 @@ def test_symlink_outside_directory_is_not_served(tmp_path: Path):
assert response.text != "secret"
def test_check_dir_true_fails_early_for_missing_directory(tmp_path: Path):
def test_check_dir_true_fails_early_for_missing_directory(monkeypatch, tmp_path: Path):
app = FastAPI()
monkeypatch.chdir(tmp_path)
with pytest.raises(RuntimeError, match="does not exist"):
app.frontend("/", directory=tmp_path / "missing")
with pytest.raises(RuntimeError, match="does not exist") as exc_info:
app.frontend("/", directory="missing")
message = str(exc_info.value)
assert "'missing'" in message
assert str(tmp_path / "missing") in message
def test_check_dir_false_allows_missing_directory_and_fails_on_request(tmp_path: Path):
@ -707,20 +712,29 @@ def test_check_dir_false_allows_missing_directory_and_fails_on_request(tmp_path:
TestClient(app).get("/asset.txt")
def test_explicit_fallback_files_fail_clearly_when_missing(tmp_path: Path):
def test_explicit_fallback_files_fail_clearly_when_missing(monkeypatch, tmp_path: Path):
dist = tmp_path / "dist"
dist.mkdir()
monkeypatch.chdir(tmp_path)
app = FastAPI()
with pytest.raises(RuntimeError, match="index.html"):
app.frontend("/", directory=dist, fallback="index.html")
with pytest.raises(RuntimeError, match="index.html") as exc_info:
app.frontend("/", directory="dist", fallback="index.html")
message = str(exc_info.value)
assert "directory 'dist'" in message
assert str(dist) in message
app = FastAPI()
app.frontend("/", directory=dist, fallback="404.html", check_dir=False)
app.frontend("/", directory="dist", fallback="404.html", check_dir=False)
with pytest.raises(RuntimeError, match="404.html"):
with pytest.raises(RuntimeError, match="404.html") as exc_info:
TestClient(app).get("/missing.js")
message = str(exc_info.value)
assert "directory 'dist'" in message
assert str(dist) in message
def test_frontend_routes_are_not_in_openapi(tmp_path: Path):
dist = tmp_path / "dist"

Loading…
Cancel
Save