Commit Graph
648 Commits
Author SHA1 Message Date
J. Nick Koston d05f4f82e1 Merge remote-tracking branch 'upstream/core-loop-wake-take-first' into integration 2026-04-30 07:52:35 -05:00
Jesse Hills e127268dac [core] Strip \\?\ prefix from sys.executable for PlatformIO subprocess (#16158) 2026-04-30 16:04:52 +12:00
J. Nick Koston 8246b09445 Merge branch 'ota-watchdogmanager' into integration 2026-04-29 14:34:36 -05:00
J. Nick Koston 985dba9332 [core] Defer heavy module-scope imports in __main__, loader, and config (#15955) 2026-04-29 13:17:59 -05:00
J. Nick Koston 44cabc191d [core] Catch body-read errors in download_content (#16023) 2026-04-29 14:06:41 -04:00
J. Nick Koston 34b66dca55 Merge remote-tracking branch 'upstream/dev' into integration
# Conflicts:
#	esphome/external_files.py
#	tests/components/wifi/test.esp8266-ard.yaml
2026-04-29 06:21:06 -05:00
J. Nick Koston 8ceada8d04 [core] Download external_files in parallel (#16021) 2026-04-29 14:32:30 +12:00
J. Nick Koston 592486ae9a [analyze_memory] Attribute main.cpp setup()/loop() to esphome core (#16033) 2026-04-29 14:06:54 +12:00
J. Nick Koston eec770d622 [core] Use ETag in external_files cache to fix re-downloads from raw.githubusercontent.com (#16020) 2026-04-29 13:52:09 +12:00
J. Nick Koston d7b21a84a3 [git] Make ref fetches and submodule updates shallow (#16014) 2026-04-29 13:49:51 +12:00
J. Nick Koston 875bd40dfe Merge remote-tracking branch 'origin/ci/lazy-import-offenders' into integration 2026-04-28 10:39:15 -05:00
J. Nick Koston e5a83555b9 Merge remote-tracking branch 'upstream/ci/import-time-check' into ci/lazy-import-offenders
# Conflicts:
#	esphome/loader.py
2026-04-28 08:51:00 -05:00
J. Nick Koston 0759a3c681 [core] Split wake.{h,cpp} into per-platform files (#15978) 2026-04-28 08:48:13 -05:00
J. Nick Koston 191115bf18 Merge remote-tracking branch 'origin/ota-wakeable-delay-yield' into integration 2026-04-27 07:55:22 -05:00
J. Nick Koston 2e096bb036 [core] Combine set_component_source_ + register_component_ into one call (#16029) 2026-04-26 21:54:15 -05:00
J. Nick Koston 84aa308557 Merge remote-tracking branch 'upstream-ssh/analyze-memory-main-cpp-attribution' into integration 2026-04-26 15:12:28 -05:00
J. Nick Koston 82ba873ea7 [analyze_memory] Attribute main.cpp setup()/loop() to esphome core
The generated src/main.cpp.o was being skipped by _scan_source_symbols
(which only walked src/esphome/) and _source_file_to_component had no
rule for it. As a result setup()/loop() fell through to the
"app_framework" heuristic in const.py instead of being counted as
[esphome]core, both via the nm scan path and the linker map path.

Scan all of src/ and recognize .../src/main.cpp.o as core.
2026-04-26 15:10:48 -05:00
J. Nick Koston 61976ca117 Merge branch 'register-component-source-overload' into integration 2026-04-26 14:38:50 -05:00
J. Nick Koston e1ca77e025 Update tests for combined register_component_ call 2026-04-26 14:19:43 -05:00
J. Nick Koston 0fadf1470c Merge remote-tracking branch 'origin/external-files-content-error' into integration
# Conflicts:
#	tests/unit_tests/test_external_files.py
2026-04-26 10:40:14 -05:00
J. Nick Koston 6e13182120 Address Copilot review on PR #16023
Replace `type(mock_response).content = property(...)` with a tiny
_BodyReadErrorResponse stub class. The previous form mutates
MagicMock's class globally, which can leak into every other
MagicMock-using test in this file.
2026-04-26 10:39:29 -05:00
J. Nick Koston a94290c08c Merge remote-tracking branch 'origin/external-files-content-error' into integration
# Conflicts:
#	esphome/external_files.py
#	tests/unit_tests/test_external_files.py
2026-04-26 10:36:05 -05:00
J. Nick Koston 24a3c2202f [core] Catch body-read errors in download_content
`requests.Response.content` reads the body lazily, so chunked-decode,
gzip-decode, and mid-stream connection drops all surface as
RequestException subclasses on first access -- not from requests.get
itself. The previous code accessed `.content` outside the surrounding
try/except, so any of those (rare but real) errors would propagate
out of download_content instead of falling back to the cached file
or raising the user-friendly cv.Invalid.

Move `data = req.content` inside the try block so the existing error
path handles it. Two new tests cover the with-cache and no-cache
branches using a `ChunkedEncodingError` injected on `.content`.
2026-04-26 10:35:06 -05:00
J. Nick Koston b22e46f090 Merge remote-tracking branch 'origin/external-files-etag' into integration 2026-04-26 10:16:00 -05:00
J. Nick Koston b303cc68f6 Cover the two _write_etag error branches
EsphomeError from write_file and OSError from os.utime are both
swallowed and logged at DEBUG; add tests so neither branch can regress
silently.
2026-04-26 10:15:54 -05:00
J. Nick Koston af52f6a170 Merge remote-tracking branch 'origin/external-files-etag' into integration
# Conflicts:
#	tests/unit_tests/test_external_files.py
2026-04-26 10:10:11 -05:00
J. Nick Koston b78bca0dc7 Simplify _read_etag, walrus the etag/new_etag binds, fixturize requests + helper patches
- _read_etag collapses into a single try/except that handles both stat
  and read failures (and the unlink) uniformly.
- _mtime_seconds() helper compares mtimes at whole-second resolution
  instead of nanoseconds. Sub-second precision varies by filesystem
  (FAT does 2s, NTFS 100ns, APFS/ext4 ns); whole seconds is the only
  resolution every supported filesystem can preserve through a
  set+read round-trip.
- has_remote_file_changed: walrus-assign `etag` and `new_etag` instead
  of an explicit binding + immediate check.
- New test fixtures `mock_requests_head`, `mock_requests_get`,
  `mock_has_remote_file_changed`, `mock_write_file` replace the
  per-test `@patch(...)` decorator stacks. Tests are simpler and the
  fixtures can be reused by future tests in the file.
2026-04-26 10:09:38 -05:00
J. Nick Koston 1cecb98f56 Merge remote-tracking branch 'origin/external-files-parallel' into integration 2026-04-26 10:03:03 -05:00
J. Nick Koston 2def20c646 Use partial() for download_web_files_in_config call sites; collapse dedup loop into a dict comp
- Both audio_file and speaker.media_player now use functools.partial to
  bind path_for=_compute_local_file_path instead of an inline lambda.
- download_content_many's dedup loop becomes a dict comprehension.
  Last-URL wins now (dict-comp semantics) instead of first-URL; in
  practice duplicate paths only arise when the URL itself is duplicated,
  so the choice is meaningless. Test updated accordingly.
2026-04-26 10:02:44 -05:00
J. Nick Koston 9153c1be67 Merge remote-tracking branch 'origin/external-files-etag' into integration 2026-04-26 09:58:29 -05:00
J. Nick Koston fea64833b0 Merge remote-tracking branch 'origin/external-files-parallel' into integration
# Conflicts:
#	tests/unit_tests/test_external_files.py
2026-04-26 09:58:27 -05:00
J. Nick Koston 267590fc36 Extract mock_download_content[_many] fixtures in test_external_files 2026-04-26 09:57:23 -05:00
J. Nick Koston a877acf1d1 Address Copilot review on PR #16021
- download_content_many now de-duplicates by `path` so two callers
  asking for the same cache file (e.g. the same URL referenced twice
  in a config) can't race on download_content's non-atomic write. When
  duplicates are present, the first-seen URL for that path wins.
- Clamp `max_workers` to at least 1 so an invalid caller value can't
  raise ValueError out of ThreadPoolExecutor.
2026-04-26 09:55:27 -05:00
J. Nick Koston da322356e1 Address Copilot review on PR #16020
- _write_etag: suppress OSError (not just FileNotFoundError) when removing
  a stale sidecar -- ETag persistence is best-effort and a permission
  error there must not abort an otherwise-successful download.
- Atomic-write test: patch write_file directly so the failure happens
  inside the helper that does the rename, instead of before
  download_content even calls it.
2026-04-26 09:54:38 -05:00
J. Nick Koston eb3bf5baed Merge remote-tracking branch 'origin/external-files-parallel' into integration
# Conflicts:
#	esphome/external_files.py
2026-04-26 09:53:29 -05:00
J. Nick Koston 54493faafc Fix download_web_files_in_config tests for generator argument 2026-04-26 09:53:07 -05:00
J. Nick Koston 540696282b Hoist duplicated _download_all_web_files into external_files.download_web_files_in_config
audio_file and speaker.media_player had byte-identical helpers that
collected web URLs from a list-of-file-configs and handed them to
download_content_many. Move that logic into external_files as
`download_web_files_in_config(config, path_for)` -- callers only have
to pass the per-component cache-path callback now.
2026-04-26 09:52:29 -05:00
J. Nick Koston 7983ebf54a Merge remote-tracking branch 'origin/external-files-etag' into integration 2026-04-26 09:42:36 -05:00
J. Nick Koston 5ae259ccba Sync ETag sidecar mtime with cache file; treat mismatched sidecars as stale
The ETag sidecar describes a specific snapshot of the cache file. If the
cache file is replaced or edited out-of-band (manual edit, restore from
backup, another tool overwriting it), the sidecar's recorded ETag no
longer matches the bytes on disk -- using it would cause the server to
return 304 and we'd serve the wrong content from cache.

- _write_etag now os.utime()'s the sidecar to share the cache file's
  st_mtime_ns immediately after writing it.
- _read_etag compares the two mtimes; if they don't match it returns
  None and removes the sidecar so subsequent calls don't re-check it.
2026-04-26 09:41:20 -05:00
J. Nick Koston 3f793e682a Merge remote-tracking branch 'origin/external-files-parallel' into integration
# Conflicts:
#	esphome/external_files.py
#	tests/unit_tests/test_external_files.py
2026-04-26 09:39:21 -05:00
J. Nick Koston 2d9d1eabfa [core] Download external_files in parallel
Each component that uses external_files (audio_file, speaker
media_player, ...) currently calls download_content once per file
inline inside a per-item config validator. With ~24 cached audio
files in a Home Assistant Voice PE config, that means ~24 sequential
HEAD round-trips, even when every response is a 304.

This adds download_content_many(items, ...) which fans the per-file
checks out across a ThreadPoolExecutor (capped at 16 workers so
configs with hundreds of files don't open hundreds of sockets), then
refactors audio_file and speaker.media_player to collect URLs at the
list level and call the batch helper once instead of downloading
inside each per-item validator.

Wall time for the validation phase drops from sum(latency) to roughly
max(latency) when the cache is warm.
2026-04-26 09:36:13 -05:00
J. Nick Koston 68b754d741 Merge remote-tracking branch 'origin/external-files-etag' into integration 2026-04-26 09:28:44 -05:00
J. Nick Koston 9f058ac1a6 [core] Use ETag in external_files cache to fix re-downloads from raw.githubusercontent.com
raw.githubusercontent.com ignores If-Modified-Since (always returns
200), but honors If-None-Match with ETag (returns 304). This caused
every esphome compile/config run to re-download every cached external
file (audio_file, micro_wake_word, image, font, bme68x_bsec2, etc.)
sourced from a /raw/ URL.

- Send If-None-Match with the cached ETag when present
- Persist the ETag from each download in a hidden sidecar file
  (.{name}.etag) and refresh it when a 304 carries a new ETag
- Replace path.write_bytes() with helpers.write_file() so downloads are
  written atomically and can no longer leave partially-written cache
  files behind on crash
2026-04-26 09:26:20 -05:00
J. Nick Koston 0f25d91e68 [core] Unify skip_external_update and honor it in external_files for faster esphome logs (#16016) 2026-04-26 07:24:33 -05:00
J. Nick Koston 8096b95910 Merge remote-tracking branch 'upstream/skip_external_files_update_logs' into integration 2026-04-26 04:51:47 -05:00
J. Nick Koston 3438ff817a Address review: drop redundant comments and rely on existing CORE.reset() between tests 2026-04-26 04:49:04 -05:00
J. Nick Koston 4654f427ca Merge remote-tracking branch 'upstream/skip_external_files_update_logs' into integration 2026-04-26 04:44:42 -05:00
J. Nick Koston 5150a32224 [external_files] Unify skip_external_update via CORE flag for git as well 2026-04-26 04:43:53 -05:00
J. Nick Koston 26ba4850e4 [external_files] Skip remote freshness checks during esphome logs
Avoid per-file HTTP HEAD requests during config validation when running
esphome logs against a previously-cached project. The skip_external_update
flag was already plumbed for git operations, but external_files.download_content
ignored it. Thread it through CORE so audio_file, micro_wake_word,
speaker/media_player, image, font, and bme68x_bsec2 reuse cached files
without per-URL network round-trips when the file already exists locally.
2026-04-26 04:37:35 -05:00
J. Nick Koston a8d767cc5c Merge remote-tracking branch 'upstream/dev' into integration 2026-04-26 04:16:55 -05:00