[core] Use uv for the ESP-IDF Python environment when available (#18838)

This commit is contained in:
J. Nick Koston
2026-08-28 13:43:48 -05:00
committed by GitHub
parent cc41fd0beb
commit 4333870590
2 changed files with 51 additions and 9 deletions
+24 -9
View File
@@ -1053,15 +1053,28 @@ def _check_esp_idf_python_env_install(
constraint_file_path,
)
cmd_pip_install = [
str(env_python_path),
"-m",
"pip",
"install",
"--upgrade",
"--constraint",
constraint_file_path,
]
# uv (much faster than pip) when available, e.g. in the docker image
if uv_path := shutil.which("uv"):
cmd_pip_install = [
uv_path,
"pip",
"install",
"--python",
str(env_python_path),
"--upgrade",
"--constraint",
str(constraint_file_path),
]
else:
cmd_pip_install = [
str(env_python_path),
"-m",
"pip",
"install",
"--upgrade",
"--constraint",
str(constraint_file_path),
]
_LOGGER.info("Installing ESP-IDF %s Python dependencies ...", version)
cmd = cmd_pip_install + [
@@ -1135,6 +1148,8 @@ def check_esp_idf_install(
env = {}
env["IDF_TOOLS_PATH"] = str(get_idf_tools_path())
env["IDF_PATH"] = ""
# uv defaults to 3 HTTP retries; match the pioarduino penv's bump to 10
env["UV_HTTP_RETRIES"] = os.environ.get("UV_HTTP_RETRIES", "10")
# An explicit ESPHOME_IDF_DEFAULT_TARGETS wins over the caller's
# per-variant request (builder-image pre-warm); otherwise the caller's
+27
View File
@@ -520,6 +520,33 @@ def test_check_esp_idf_install_feature_failure(espidf_mocks: SimpleNamespace) ->
check_esp_idf_install(_IDF_VERSION, force=True, features=["fb"])
def test_python_deps_use_uv_when_available(
espidf_mocks: SimpleNamespace, monkeypatch: pytest.MonkeyPatch
) -> None:
"""The python env installs go through uv when on the PATH, pip otherwise."""
monkeypatch.delenv("UV_HTTP_RETRIES", raising=False)
with patch(
"esphome.espidf.framework.shutil.which",
# Keyed on the name: the same which() also probes the default tools
side_effect=lambda name: "/usr/bin/uv" if name == "uv" else None,
):
check_esp_idf_install(_IDF_VERSION, force=True, features=["fb"])
upgrade_call, feature_call = espidf_mocks.run_ok.call_args_list[1:3]
upgrade_cmd, feature_cmd = upgrade_call.args[0], feature_call.args[0]
assert upgrade_cmd[:3] == ["/usr/bin/uv", "pip", "install"]
assert "--python" in upgrade_cmd
assert feature_cmd[:3] == ["/usr/bin/uv", "pip", "install"]
assert upgrade_call.kwargs["env"]["UV_HTTP_RETRIES"] == "10"
espidf_mocks.run_ok.reset_mock()
monkeypatch.setenv("UV_HTTP_RETRIES", "3") # an explicit user value wins
with patch("esphome.espidf.framework.shutil.which", return_value=None):
check_esp_idf_install(_IDF_VERSION, force=True, features=["fb"])
upgrade_call = espidf_mocks.run_ok.call_args_list[1]
assert upgrade_call.args[0][1:4] == ["-m", "pip", "install"]
assert upgrade_call.kwargs["env"]["UV_HTTP_RETRIES"] == "3"
def _mark_installed() -> None:
"""Create the extracted marker and python-env interpreter so the install
check takes the already-installed path rather than force-installing."""