From cb31610d5a06b6f5fd21ecdea2850c085f276fee Mon Sep 17 00:00:00 2001 From: Agastya Kukudala Date: Tue, 13 Oct 2026 15:26:41 -0500 Subject: [PATCH] fix: report malformed pyproject TOML as a CLI error --- src/fastapi_cli/cli.py | 4 ++++ src/fastapi_cli/config.py | 7 ++++++- tests/test_cli_pyproject.py | 21 +++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/fastapi_cli/cli.py b/src/fastapi_cli/cli.py index c3dccbae..46b27175 100644 --- a/src/fastapi_cli/cli.py +++ b/src/fastapi_cli/cli.py @@ -165,6 +165,10 @@ def _run( try: config = FastAPIConfig.resolve(entrypoint=entrypoint) + except FastAPICLIException as e: + toolkit.print_line() + toolkit.print(f"[error]{e}") + raise typer.Exit(code=1) from None except ValidationError as e: toolkit.print_line() toolkit.print("[error]Invalid configuration in pyproject.toml:") diff --git a/src/fastapi_cli/config.py b/src/fastapi_cli/config.py index 1a8b84d8..22c30dbb 100644 --- a/src/fastapi_cli/config.py +++ b/src/fastapi_cli/config.py @@ -4,6 +4,8 @@ from pydantic import BaseModel, StrictStr +from fastapi_cli.exceptions import FastAPICLIException + logger = logging.getLogger(__name__) @@ -29,7 +31,10 @@ def _read_pyproject_toml(cls) -> dict[str, Any]: return {} with open(pyproject_path, "rb") as f: - data = tomllib.load(f) + try: + data = tomllib.load(f) + except tomllib.TOMLDecodeError as e: + raise FastAPICLIException(f"Invalid TOML in pyproject.toml: {e}") from e return data.get("tool", {}).get("fastapi", {}) # type: ignore[no-any-return] diff --git a/tests/test_cli_pyproject.py b/tests/test_cli_pyproject.py index f6b36890..1c6a9816 100644 --- a/tests/test_cli_pyproject.py +++ b/tests/test_cli_pyproject.py @@ -1,6 +1,7 @@ from pathlib import Path from unittest.mock import patch +import pytest import uvicorn from typer.testing import CliRunner @@ -12,6 +13,26 @@ assets_path = Path(__file__).parent / "assets" +@pytest.mark.parametrize("command", ["dev", "run"]) +@pytest.mark.parametrize( + "content", + [ + "[tool.fastapi\n", + '[tool.fastapi]\nentrypoint = "main:app"\nentrypoint = "other:app"\n', + ], +) +def test_pyproject_toml_syntax_error( + command: str, content: str, tmp_path: Path +) -> None: + (tmp_path / "pyproject.toml").write_text(content) + with changing_dir(tmp_path), patch.object(uvicorn, "run") as mock_run: + result = runner.invoke(app, [command]) + + assert result.exit_code == 1 + assert "Invalid TOML in pyproject.toml:" in result.output + mock_run.assert_not_called() + + def test_dev_with_pyproject_app_config_uses() -> None: with ( changing_dir(assets_path / "pyproject_config"),