Skip to content

sync_components: consume OnlyWith gated defaults from schema bundle#360

Merged
bdraco merged 5 commits intomainfrom
feature/onlywith-gated-defaults
May 6, 2026
Merged

sync_components: consume OnlyWith gated defaults from schema bundle#360
bdraco merged 5 commits intomainfrom
feature/onlywith-gated-defaults

Conversation

@bdraco
Copy link
Copy Markdown
Member

@bdraco bdraco commented May 6, 2026

What does this implement/fix?

Companion to esphome/esphome#16276, which adds a new default_with: {value, components} (and inverse default_without) field to the schema bundle so cv.OnlyWith defaults — previously dropped entirely because the validator's default property gates on CORE.loaded_integrations (always empty at schema-generation time) — survive into schema.esphome.io.

This PR teaches script/sync_components.py to read the new shape: when a field carries default_with, the catalog entry's default_value and depends_on_component are populated from default_with.value and default_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 as cv.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_coexistence would 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.io publish) ship default only — those flow through _extract_default's existing path unchanged, returning (value, None). The new branch only fires when default_with appears 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.io publish 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

  • Multi-component OnlyWith: cv.OnlyWith supports a list of components (all must be loaded). depends_on_component is 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_component can'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 is esp32_ble_beacon.tx_power defaulting to "no value" instead of "3dBm when esp32_hosted is absent".

Types of changes

  • Bugfix (non-breaking change which fixes an issue) — bugfix
  • New feature (non-breaking change which adds functionality) — new-feature
  • Enhancement to an existing feature — enhancement
  • Breaking change (fix or feature that would cause existing functionality to not work as expected) — breaking-change
  • Refactor (no behaviour change) — refactor
  • Documentation only — docs
  • Maintenance / chore — maintenance
  • CI / workflow change — ci
  • Dependencies bump — dependencies

Related issue or feature (if applicable):

Frontend coordination

  • No frontend change needed
  • Companion frontend PR: esphome/device-builder-dashboard-frontend#

The frontend already reads default_value (PR #181 wired the boolean fallback) and depends_on_component (long-standing). The catalog entries this PR produces fit the existing shape; no frontend change required.

Checklist

  • The code change is tested and works locally.
  • Pre-commit hooks pass (ruff, codespell, yaml/json/python checks).
  • Tests have been added or updated under tests/ where applicable.
  • components.json has not been hand-edited (the catalog regen happens via script/sync_components.py after the upstream schema ships).
  • Architecture-level changes are reflected in docs/ARCHITECTURE.md and/or docs/API.md.

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.
@bdraco bdraco added the enhancement Improvement to an existing feature label May 6, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 6, 2026

Merging this PR will not alter performance

✅ 11 untouched benchmarks


Comparing feature/onlywith-gated-defaults (96cbe28) with main (c1b88a2)

Open in CodSpeed

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.64%. Comparing base (71a79c5) to head (3f5934f).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #360   +/-   ##
=======================================
  Coverage   98.64%   98.64%           
=======================================
  Files          52       52           
  Lines        5911     5911           
=======================================
  Hits         5831     5831           
  Misses         80       80           
Flag Coverage Δ
py3.12 98.59% <ø> (ø)
py3.14 98.64% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.
@bdraco bdraco requested a review from Copilot May 6, 2026 21:01
@bdraco bdraco marked this pull request as ready for review May 6, 2026 21:05
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py to extract defaults from default_with and populate default_value + depends_on_component accordingly (while leaving default_without unsupported 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 thread script/sync_components.py Outdated
Comment thread script/sync_components.py Outdated
Comment thread tests/test_sync_components_default_extraction.py Outdated
bdraco and others added 3 commits May 6, 2026 16:27
- 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.
@bdraco
Copy link
Copy Markdown
Member Author

bdraco commented May 6, 2026

Won't actually work till the new ESPHome release, but thats fine

@bdraco bdraco merged commit 230e2a0 into main May 6, 2026
7 of 11 checks passed
@bdraco bdraco deleted the feature/onlywith-gated-defaults branch May 6, 2026 21:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement to an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants