sync_components: consume OnlyWith gated defaults from schema bundle#360
sync_components: consume OnlyWith gated defaults from schema bundle#360
Conversation
Companion to esphome/esphome#16276, which surfaces cv.OnlyWith defaults under a new schema-bundle field default_with: {value, components} that pairs the unconditional default with the component(s) gating it. Without this companion change the device-builder catalog generator silently drops those defaults on the floor (and the dashboard renders e.g. esp32_ble_tracker.software_coexistence as a default-OFF toggle even when the user has wifi: configured). _extract_default() resolves the new shape into the (default_value, depends_on_component) pair the catalog entry expects. depends_on_component takes the first listed component (a single string today; multi-component OnlyWith calls — none exist upstream yet — log a WARNING and pick the first). cv.OnlyWithout (default_without) has inverse-gate semantics that depends_on_component can't model today; left for a follow-up. Pure addition: schemas predating #16276 still ship the plain default field and flow through the existing path unchanged.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #360 +/- ##
=======================================
Coverage 98.64% 98.64%
=======================================
Files 52 52
Lines 5911 5911
=======================================
Hits 5831 5831
Misses 80 80
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
Adds three real raw-schema fixtures captured from running the patched build_language_schema.py (esphome/esphome#16276) against ESPHome's tree on 2026-05-06: - esp32_ble_tracker.software_coexistence (default_with, boolean) - zigbee.power_source (default_with, enum) - esp32_ble_beacon.tx_power (default_without, enum) Three layers of coverage: 1. _extract_default unit tests over synthetic shapes (existing). 2. _extract_default fixture tests against the real raw entries. 3. _convert_field end-to-end tests against the same fixtures, asserting the produced catalog dict has the right default_value + depends_on_component + type + options shape. Layer 3 catches a future change anywhere in the conversion pipeline that quietly drops the gate or default. Layer 2 is the canary if upstream changes the schema field name.
There was a problem hiding this comment.
Pull request overview
This PR updates the component-catalog sync pipeline to preserve ESPHome schema defaults gated by cv.OnlyWith (new default_with / default_without shape from the schema bundle), so schema.esphome.io-derived catalog entries can carry both a conditional default value and its gating component.
Changes:
- Teach
script/sync_components.pyto extract defaults fromdefault_withand populatedefault_value+depends_on_componentaccordingly (while leavingdefault_withoutunsupported for now). - Add unit + end-to-end conversion tests to pin default extraction behavior across old (
default) and new (default_with) schema shapes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
script/sync_components.py |
Adds _extract_default() and wires it into _convert_field() to support default_with gated defaults. |
tests/test_sync_components_default_extraction.py |
New tests covering _extract_default() and _convert_field() behavior for unconditional and gated defaults. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Comment said 'prefer unconditional default, fall back to default_with' but the code prefers default_with (and the new unit test pinned that). Updated the comment to match the actual precedence; explained the rationale (gated semantics are the more specific contract). - _extract_default's multi-component warning was print()'d to stdout — switched to _LOGGER.warning() to match the rest of the script, threaded the field key through so the message has context. - Test now uses caplog (not capsys) and asserts on the warning record, matching the new logging path.
Block comment above the _extract_default call dropped — the helper's own docstring already explains what's happening, and the call site doesn't need a 14-line preface. Docstrings on _extract_default and the test functions trimmed to a single explanatory sentence each. Behavior unchanged; 13/13 tests still pass.
|
Won't actually work till the new ESPHome release, but thats fine |
What does this implement/fix?
Companion to esphome/esphome#16276, which adds a new
default_with: {value, components}(and inversedefault_without) field to the schema bundle socv.OnlyWithdefaults — previously dropped entirely because the validator'sdefaultproperty gates onCORE.loaded_integrations(always empty at schema-generation time) — survive intoschema.esphome.io.This PR teaches
script/sync_components.pyto read the new shape: when a field carriesdefault_with, the catalog entry'sdefault_valueanddepends_on_componentare populated fromdefault_with.valueanddefault_with.components[0]respectively. That gives the dashboard the gating context to apply the default conditionally instead of unconditionally.The motivating case:
esp32_ble_tracker.software_coexistence— declared upstream ascv.OnlyWith(K, "wifi", default=True). With this PR + the upstream change, the schema-derived catalog entry ships:{ "key": "software_coexistence", "default_value": true, "depends_on_component": "wifi" }so the dashboard's frontend renders the toggle ON when the user has
wifi:configured, and hides the field entirely when they don't (matching ESPHome's runtime "no default applies" behaviour). Without this PR (or with a pre-#16276 schema bundle),software_coexistencewould continue to surface as a default-less, always-OFF toggle even when wifi is configured.Backwards compatibility
Pure addition. Schemas predating #16276 (the current
schema.esphome.iopublish) shipdefaultonly — those flow through_extract_default's existing path unchanged, returning(value, None). The new branch only fires whendefault_withappears in the raw schema entry, which won't happen until the next ESPHome release publishes a bundle generated by the patched script.This PR is therefore a no-op against the current
schema.esphome.iopublish and only starts producing different output once #16276 lands and ships in a release. Tests pin the resolver behaviour against synthetic raw inputs so the wiring stays correct in both schema eras.Carve-outs
cv.OnlyWithsupports a list of components (all must be loaded).depends_on_componentis a single-string field; multi-component gates would need a list. No upstream OnlyWith uses a list today (verified against ESPHome's five existing call sites — all single-component); the resolver picks the first and logs a WARNING. Future extension if a real call site needs it.cv.OnlyWithout(default_without): inverse-gate semantics — "default applies when this component is NOT loaded".depends_on_componentcan't model the inverted gate today, so we surface no default for these fields (no regression — same as before #16276 when the schema dropped them entirely). Tracked as a follow-up; only impact isesp32_ble_beacon.tx_powerdefaulting to "no value" instead of "3dBm when esp32_hosted is absent".Types of changes
bugfixnew-featureenhancementbreaking-changerefactordocsmaintenancecidependenciesRelated issue or feature (if applicable):
Frontend coordination
The frontend already reads
default_value(PR #181 wired the boolean fallback) anddepends_on_component(long-standing). The catalog entries this PR produces fit the existing shape; no frontend change required.Checklist
ruff,codespell, yaml/json/python checks).tests/where applicable.components.jsonhas not been hand-edited (the catalog regen happens viascript/sync_components.pyafter the upstream schema ships).docs/ARCHITECTURE.mdand/ordocs/API.md.