Skip to content

Fix be when i want the catalog for a specific device#2

Closed
stvncode wants to merge 1 commit into
mainfrom
fix/be
Closed

Fix be when i want the catalog for a specific device#2
stvncode wants to merge 1 commit into
mainfrom
fix/be

Conversation

@stvncode
Copy link
Copy Markdown
Collaborator

@stvncode stvncode commented Apr 2, 2026

  • .components.dashboard_import fix the problem when trying to run the servor
  • and the removal of the to_dict an error server error when trying to reach the catalog in the fe

@stvncode stvncode self-assigned this Apr 2, 2026
Copilot AI review requested due to automatic review settings April 2, 2026 10:45
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 backend handlers to return proper aiohttp.web.Response objects for catalog-style endpoints and adjusts the ESPHome import_config import to a path that matches newer ESPHome layouts.

Changes:

  • Remove erroneous .to_dict() calls on json_response(...) results for section/config catalog endpoints.
  • Update import_config import to esphome.components.dashboard_import to fix server startup/import issues.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
esphome_device_builder/handlers/section_config.py Fixes the section-config GET endpoint to return a web.Response (avoids calling .to_dict() on a response).
esphome_device_builder/handlers/devices.py Updates the import_config import path to align with ESPHome’s dashboard_import module.
esphome_device_builder/handlers/config_sections.py Fixes the config catalog endpoint to return a web.Response (avoids calling .to_dict() on a response).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@stvncode stvncode added the bug label Apr 2, 2026
@marcelveldt marcelveldt closed this Apr 2, 2026
bdraco added a commit that referenced this pull request May 2, 2026
- ``DeviceScanner.get_by_name`` now returns a fresh list snapshot.
  Previously the method handed out a reference to the live bucket;
  a misbehaving caller could mutate it (``.clear()``, ``.sort()``)
  and quietly poison the name index for future lookups. Matches the
  semantics of the ``devices`` property.
- ``_set_device`` keeps each bucket sorted by ``configuration``
  filename. Insertion order used to depend on ``paths_to_load``
  (a set), so ``bucket[0]`` consumers — including
  ``_find_device_by_name`` and the apply / dedupe path — saw a
  non-deterministic "first match" for duplicate-named YAMLs and
  could flip-flop across scans.
- ``apply()`` now dedupes against every matching device's state,
  not just the first. With two YAMLs sharing an ``esphome.name``,
  one sibling could be in-sync while the other was rebuilt with
  state=UNKNOWN; the old "first device matches → bail" path left
  the stale sibling stuck.
- Reworded the priority-2 docstring in ``compute_has_pending_changes``
  to reflect the actual condition (no comparable hash pair AND no
  local binary), since with the reorder #2 can fire even when one
  hash side has been broadcast.
- New regression tests:
  - ``test_get_by_name_returns_a_snapshot``
  - ``test_index_bucket_order_is_deterministic``
  - ``test_apply_state_repairs_stale_sibling_when_first_match_is_in_sync``
bdraco added a commit that referenced this pull request May 7, 2026
Three real bugs Copilot flagged in the YAML rewriter and clone
guards. Fix all three; add coverage for each.

**1. ``rewrite_yaml_scalar`` matched off-path nested keys.**
The walker only pushed *on-path* mapping keys onto its ancestor
stack. For path ``("api", "encryption", "key")``, YAML like
``api: { something: { encryption: { key: ... } } }`` would
falsely satisfy the path comparison because ``something`` was
invisible to the ancestor check. Push every mapping key (on-path
or not) so off-path branches show up in the ancestor chain and
the comparison fails correctly.

**2. ``.yaml`` vs ``.yml`` extension slipped past the same-name
guard.** ``configuration="kitchen.yml"`` + ``new_name="kitchen"``
produced ``new_filename="kitchen.yaml"``, which doesn't equal the
configuration string, so the same-name check passed — but both
files would carry ``esphome.name: kitchen`` and collide on mDNS
once the clone was flashed. Compare on stems instead of filenames.

**3. Friendly names with YAML-special characters got silently
truncated / split.** A friendly name like ``Bedroom #2`` written
as a plain scalar would round-trip as just ``Bedroom`` (everything
after `` #`` becomes a YAML comment); ``Lamp: Kitchen`` would
split into a key/value pair. Add ``_safe_yaml_scalar`` which emits
plain scalars when safe and double-quoted otherwise. Route
``rewrite_name_or_substitution`` and the encryption-key rewrite
through it.

DRY pass:
- Extract ``_strip_yaml_quotes`` shared by ``rewrite_esphome_name``
  and ``parse_substitution_ref``.
- Drop the unused ``rewrite_friendly_name`` (clone now goes
  through ``rewrite_name_or_substitution``).

22 new / updated tests pin the off-path soundness fix, the
``.yml`` collision rejection, the safe-quoting matrix, and the
``_strip_yaml_quotes`` helper. 1811/1811 backend tests pass.
bdraco added a commit that referenced this pull request May 7, 2026
Closes esphome/feature-requests#2868. The use case that drives
this: someone gets a Made-For-ESPHome device, adopts it through
the dashboard, and now wants to call it "Reading Lamp" instead of
the manufacturer's default friendly_name. They don't know YAML
yet — opening the editor and finding ``esphome:`` ->
``friendly_name:`` is a wall they shouldn't have to scale just to
rename their bulb.

The existing ``devices/update`` only writes the metadata sidecar
JSON, not the YAML, so wiring a UI to it would let the dashboard
label drift from what the running firmware broadcasts. The new
command rewrites the YAML directly via the same
``rewrite_name_or_substitution`` machinery the clone path is
built on — substitution-aware (``friendly_name: ${friendly_name}``
redirects through the substitutions block) and safe on
YAML-special characters (``Bedroom #2`` round-trips through
``_safe_yaml_scalar`` quoting).

The install half stays out of this command. The frontend already
owns the OTA UX (opens the command-dialog, follows the streaming
job) so a separate ``firmware/install`` call after this one
composes naturally with the existing rename / install paths.

DRY pass: the "leaf must exist in this file" precondition + the
rewrite were inlined in clone_device. Extracted into a shared
``_rewrite_required_yaml_leaf`` helper so the pattern lives in
one place.

Eight new tests cover the literal-leaf rewrite, substitution
redirect, YAML-special quoting, idempotent no-op, all three
INVALID_ARGS paths, and lookalike-line preservation.
bdraco added a commit that referenced this pull request May 9, 2026
The orphan was the actual bug, not real DNS. The previous commit's
test patches were the right shape (avoid network in tests), but
they masked the underlying lifecycle race — which is why the same
hang resurfaced under pytest-timeout on Linux + Python 3.14 +
non-stable esphome.

Race timeline (real or stubbed network — same shape either way):

* request_pair #1 spawns listener_v1, dict[key] = v1.
* listener_v1 runs to its 'await peer_link_await_pair_status'
  parked state.
* request_pair #2 calls _cancel_pair_status_listener (pops v1,
  cancels) then _spawn_pair_status_listener (dict[key] = v2).
* The test awaits listener_v1; the loop wakes v1 with
  CancelledError at the parked await. v1 enters its finally,
  which unconditionally popped dict[key] — but the slot now
  holds v2. v2 is silently evicted from the listener registry.
* unpair calls _cancel_pair_status_listener — dict empty,
  no-op. v2 is orphaned, parked forever; 'await listener_v2'
  hangs (wedged event loop, selector timeout=-1, no scheduled
  work — exactly what the failing pytest-timeout dump showed).

Locally the bug was masked because v1 typically gets cancelled
while still parked at run_in_executor (its initial identity
load), so the cancel raises before the body's try block — v1's
finally never runs and never evicts v2. On Linux + Python 3.14 +
beta/dev esphome the scheduling order was different: v1 had
time to advance to peer_link_await_pair_status before being
cancelled, the finally fired, the eviction happened, the test
hung.

Fix: only clear the dict slot if it still points at the
current task. asyncio.current_task() compared against the
stored task is the canonical 'am I still the registered
listener for this key' check. Successor v2 stays in the
registry, unpair cancels it cleanly, no orphan.

Test: forced the race deterministically. The fake
peer_link_await_pair_status now sets a per-call asyncio.Event
when it parks; the test waits on the first such event before
the second request_pair so v1 is guaranteed to be in the
critical section when cancellation arrives. Verified that the
test deterministically hangs without the controller fix (8s
timeout fires) and passes in 0.04s with it. The full
remote_build test suite (184 tests) is green.
bdraco added a commit that referenced this pull request May 10, 2026
Three fixes from the latest Copilot pass:

* `_dispatch_submit_job_ack` only copies `reason` through
  when `accepted=False`. `SubmitJobAckFrameData.reason` is
  `NotRequired` and means "rejection code on negative ack";
  the previous shape would have propagated a spurious
  `reason` on a positive ack if the receiver ever included
  one off-contract. Now drops + debug-logs the stray field
  instead.

* `_send_submit_job_frames` streams chunks via
  `chunk_bundle`'s generator instead of materialising the
  full list up front. `num_chunks` is computed via integer
  ceil on `total_bundle_bytes` so the header still announces
  the exact count without holding every slice alive
  simultaneously. Cuts peak memory roughly in half on the
  bundle hot path (up to `BUNDLE_MAX_TOTAL_BYTES`, 4 MiB).

* `_validate_submit_job_config` now returns
  `(name, yaml_path)` so the downstream
  `_build_submit_job_bundle` reuses the already-resolved
  `Path` instead of calling `rel_path` again. Drops a
  duplicate blocking `Path.resolve` syscall on every submit.
bdraco added a commit that referenced this pull request May 11, 2026
* On-disk validation of cleanup_ttl_seconds (Copilot). The WS
  validator on set_settings gates write-through-WS, but the
  on-disk decode path didn't apply the same not_bool / range
  check. A sidecar with cleanup_ttl_seconds: true would
  deserialise as 1 (bool is an int subclass) and the sweep
  would treat anything older than 1s as cold. Other wrong
  types (string, float, None) would propagate to the sweep's
  now - ttl_seconds arithmetic and raise TypeError every
  cycle.

  New __post_init__ on RemoteBuildSettings coerces non-int /
  bool back to DEFAULT_CLEANUP_TTL_SECONDS and clamps the
  result to [MIN, MAX]. Doesn't reject the row (no
  ValueError) — the load path stays robust against
  partially-corrupt sidecars; the operator's last-good enabled
  toggle survives even if the TTL field is broken. Six new
  tests in test_config_controller.py pin the coerce-to-default
  branches, the clamp-to-MIN / clamp-to-MAX branches, and the
  pass-through-on-valid branch.

* Public seam on FirmwareController for in-flight remote-peer
  jobs (reviewer concern #1). Reaching into firmware._jobs
  from the cleanup loop coupled the two controllers to the
  private dict shape — a future refactor (lock-wrapped jobs,
  QUEUED + RUNNING split into two dicts, indexed view) would
  silently break this loop. New FirmwareController.active_remote_peer_jobs
  generator filters to QUEUED / RUNNING + non-empty remote_peer.
  The cleanup loop now consumes through that seam. Two new
  tests pin every filter branch (local skip, remote queued /
  running yielded, remote completed skipped, empty case).

* Named constant for the parse_from_configuration tail-segment
  count (reviewer nit). The +3 magic was folklore; replaced
  with _TAIL_SEGMENT_COUNT and a docstring explaining the
  three required tail parts (dashboard_id, device_name, YAML
  filename under the device subtree).

Reviewer concern #2 (RAM-canonical settings) and #4
(rate-limit per cycle) deferred per their "worth a small
follow-up" framing. Concern #3 (first-sweep latency) was
considered but YAGNI for this PR: the receiver-side cleanup
ships ahead of any production user (alpha, ~10 beta testers),
so no accumulated subtrees exist before this code's first
cycle — and the default 24h TTL means even a fresh receiver
has nothing to reclaim for the first day regardless.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants