Use cached files on network errors in external_files (#14055)

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jesserockz <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Copilot
2026-03-02 11:41:20 -05:00
committed by GitHub
parent 3160457ca6
commit 77a7cbcffd
2 changed files with 53 additions and 6 deletions
+12 -3
View File
@@ -55,10 +55,12 @@ def has_remote_file_changed(url: str, local_file_path: Path) -> bool:
_LOGGER.debug("has_remote_file_changed: File modified")
return True
except requests.exceptions.RequestException as e:
raise cv.Invalid(
f"Could not check if {url} has changed, please check if file exists "
f"({e})"
_LOGGER.warning(
"Could not check if %s has changed due to network error (%s), using cached file",
url,
e,
)
return False
_LOGGER.debug("has_remote_file_changed: File doesn't exists at %s", local_file_path)
return True
@@ -98,6 +100,13 @@ def download_content(url: str, path: Path, timeout=NETWORK_TIMEOUT) -> bytes:
)
req.raise_for_status()
except requests.exceptions.RequestException as e:
if path.exists():
_LOGGER.warning(
"Could not download from %s due to network error (%s), using cached file",
url,
e,
)
return path.read_bytes()
raise cv.Invalid(f"Could not download from {url}: {e}")
path.parent.mkdir(parents=True, exist_ok=True)
+41 -3
View File
@@ -144,16 +144,16 @@ def test_has_remote_file_changed_no_local_file(setup_core: Path) -> None:
def test_has_remote_file_changed_network_error(
mock_head: MagicMock, setup_core: Path
) -> None:
"""Test has_remote_file_changed handles network errors gracefully."""
"""Test has_remote_file_changed returns False on network error when file is cached."""
test_file = setup_core / "cached.txt"
test_file.write_text("cached content")
mock_head.side_effect = requests.exceptions.RequestException("Network error")
url = "https://example.com/file.txt"
result = external_files.has_remote_file_changed(url, test_file)
with pytest.raises(Invalid, match="Could not check if.*Network error"):
external_files.has_remote_file_changed(url, test_file)
assert result is False
@patch("esphome.external_files.requests.head")
@@ -198,3 +198,41 @@ def test_is_file_recent_handles_float_seconds(setup_core: Path) -> None:
result = external_files.is_file_recent(test_file, refresh)
assert result is True
@patch("esphome.external_files.requests.get")
@patch("esphome.external_files.has_remote_file_changed")
def test_download_content_with_network_error_uses_cache(
mock_has_changed: MagicMock, mock_get: MagicMock, setup_core: Path
) -> None:
"""Test download_content uses cached file when network fails."""
test_file = setup_core / "cached.txt"
cached_content = b"cached content"
test_file.write_bytes(cached_content)
# Simulate file has changed, so it tries to download
mock_has_changed.return_value = True
mock_get.side_effect = requests.exceptions.RequestException("Network error")
url = "https://example.com/file.txt"
result = external_files.download_content(url, test_file)
assert result == cached_content
@patch("esphome.external_files.requests.get")
@patch("esphome.external_files.has_remote_file_changed")
def test_download_content_with_network_error_no_cache_fails(
mock_has_changed: MagicMock, mock_get: MagicMock, setup_core: Path
) -> None:
"""Test download_content raises error when network fails and no cache exists."""
test_file = setup_core / "nonexistent.txt"
# Simulate file has changed (doesn't exist), so it tries to download
mock_has_changed.return_value = True
mock_get.side_effect = requests.exceptions.RequestException("Network error")
url = "https://example.com/file.txt"
with pytest.raises(Invalid, match="Could not download from.*Network error"):
external_files.download_content(url, test_file)