From 4520f27d94c5d512a4c90abb39b3dc52b11d932b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 20 Aug 2026 06:17:10 -0500 Subject: [PATCH] Raise the actionable ninja message when the package itself is missing --- esphome/arduino8266/framework.py | 8 +++++++- tests/unit_tests/test_arduino8266_framework.py | 10 ++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/esphome/arduino8266/framework.py b/esphome/arduino8266/framework.py index 63f4992a9c..cac7bd9905 100644 --- a/esphome/arduino8266/framework.py +++ b/esphome/arduino8266/framework.py @@ -208,7 +208,13 @@ def _find_ninja() -> Path: """ if binary := shutil.which("ninja"): return Path(binary) - import ninja + try: + import ninja + except ImportError as err: + raise EsphomeError( + "ninja not found on PATH or in the ninja package; reinstall the " + "esphome Python environment" + ) from err binary = Path(ninja.BIN_DIR) / ("ninja.exe" if os.name == "nt" else "ninja") if not binary.is_file(): diff --git a/tests/unit_tests/test_arduino8266_framework.py b/tests/unit_tests/test_arduino8266_framework.py index 810f9aee44..f786d22ba8 100644 --- a/tests/unit_tests/test_arduino8266_framework.py +++ b/tests/unit_tests/test_arduino8266_framework.py @@ -237,6 +237,16 @@ def test_find_ninja_falls_back_to_wheel(tmp_path: Path) -> None: assert framework._find_ninja() == tmp_path / binary_name +def test_find_ninja_package_not_installed() -> None: + """A missing ninja package raises the actionable message, not ImportError.""" + with ( + patch("shutil.which", return_value=None), + patch.dict(sys.modules, {"ninja": None}), + pytest.raises(EsphomeError, match="ninja not found"), + ): + framework._find_ninja() + + def test_find_ninja_missing_everywhere(tmp_path: Path) -> None: wheel = MagicMock(BIN_DIR=str(tmp_path)) with (