Add GitHub workflow for syncing HA device classes + constants#812
Draft
TheJulianJES wants to merge 17 commits into
Draft
Add GitHub workflow for syncing HA device classes + constants#812TheJulianJES wants to merge 17 commits into
TheJulianJES wants to merge 17 commits into
Conversation
Runs tools/compare_constants.py on a daily schedule (and on demand) to surface drift between ZHA's local copies of Home Assistant constants and the canonical homeassistant package. Unlike ESPHome's equivalent, the script only detects drift, so the job fails the run instead of opening a fix-up pull request. Home Assistant is installed directly in the workflow (Python 3.14), so it no longer needs to be a project dependency.
The device class sync workflow now installs homeassistant directly, so it no longer needs to be a project testing dependency (added in #810). This reverts the large uv.lock expansion that pulling in the full Home Assistant dependency tree required.
The comparison script can now apply the safe, mechanical fixes in place instead of only reporting drift: enum members HA has but ZHA is missing are added, and enum-member / module-level-constant value mismatches are corrected to HA's value. Edits are AST-located and match the file's existing member style (contiguous unit enums vs. blank-separated device-class enums), so ruff format leaves them untouched. Ambiguous drift (ZHA-only symbols, type mismatches, entirely new enums HA has that ZHA doesn't mirror) is still only reported, never written, so a human decides those. This is what lets the sync workflow open a PR.
Rework the workflow from failing on drift to the ESPHome-style model: run compare_constants.py --write, then open/update a pull request with the safe fixes via peter-evans/create-pull-request. A final check step still fails the run if drift remains that --write could not safely resolve, so it is surfaced rather than silently dropped.
Swap the compare-and-surgically-fix approach for a straight copy: read each mirrored enum from the installed homeassistant package with inspect.getsource and write it verbatim into ZHA, docstrings and comments included. This also catches docstring drift the member-level comparison missed (e.g. SensorDeviceClass unit-of-measurement prose). Device-class / mode enums are replaced in place; every UnitOf* enum HA defines is copied into zha.units, appending any ZHA is missing. Only enums are synced — module-level constants stay as ZHA's plain literals (HA derives them from the enums, so copying verbatim would reorder the file and create forward references) and ZHA-only symbols are never touched. --check does a dry run.
Rework the workflow to copy from Home Assistant instead of surgically fixing: check out home-assistant/core at dev, install it editable alongside ZHA, run tools/sync_constants, ruff format, and open/update a pull request. The PR uses a fixed sync/device-classes branch so each run updates the same PR. Authenticate with a GitHub App token (actions/create-github-app-token) so the PR is authored by the bot account and triggers CI; the App ID and private key are read from the SYNC_APP_ID variable and SYNC_APP_PRIVATE_KEY secret.
…ection Home Assistant added UnitOfDensity and UnitOfRatio and now derives the CONCENTRATION_* and PERCENTAGE constants from them. Mirror the two enums in zha.units and move the module-level constants ZHA exposes into a single backwards-compatibility section at the end of the file, deriving the same ones from the enums exactly as HA does (values are unchanged, so existing imports keep working). The constants sit after all enums so the enum references resolve, and the section is maintained by hand: tools/sync_constants.py syncs the enums but never rewrites it. ZHA-only constants (COUNT, KILOJOULES_PER_KG, CONCENTRATION_PARTS_PER_CUBIC_METER) are kept and labelled.
The sync tool reads HA's enums and parses ZHA's source files as text rather than importing the platform modules, so it no longer pulls in zhaquirks. Installing zha-quirks (which now depends on ZHA) is unnecessary.
sync_constants now deletes ZHA unit enums HA no longer defines (they exist only to mirror HA, so no allowlist is needed) alongside adding/refreshing the current ones; enum-member removals already came through the verbatim class copy. New enums are inserted just above an explicit marker comment in zha.units rather than merely 'after the last class', keeping them in the enum region and out of the hand-maintained backwards-compatibility section below it. The tool now parses zha/units.py as text instead of importing it, so a prior sync that left a dangling reference can still be repaired. Whole device-class enum removal raises a clear 'update DEVICE_CLASS_ENUMS' error instead of a raw traceback.
Drop the stale zha-quirks requirement (the tool parses ZHA's files as text rather than importing them, so neither zha nor zha-quirks needs to be installed), and note that enums are added/refreshed/removed and that the backwards-compatibility constants section is left alone.
The summary said the tool 'removes nothing', contradicting the enum auto-removal it now performs. Clarify that only enums are added, changed, or removed, and constants / ZHA-only symbols are never touched.
- Scope the pull request commit to zha/ (add-paths) so the HA checkout under lib/ can never be committed even if .gitignore changes. - Don't install ZHA in the workflow; the tool only imports homeassistant and reads ZHA's files as text, and installing ZHA forced a joint resolution with HA-dev's pins for no benefit. - Guard enum removal: only delete a ZHA UnitOf* enum when HA no longer exposes the name at all. If HA still exposes it (e.g. moved out of homeassistant.const but re-exported), raise instead of silently deleting. - Add a concurrency group so a manual dispatch can't race the scheduled run on the shared sync/device-classes branch.
The sync tool already emits ruff-clean output and the pull request's own pre-commit CI runs the repo-pinned ruff, so the separate uvx ruff step was redundant and risked drifting from pyproject's pin. Instead, capture the tool's summary of what changed and use it as the PR body (via body-path), so each run's PR shows what it did; create-pull-request updates the body of the existing PR on later runs.
Clarify in the README that only unit enums are auto-added/removed while the device-class enums are a fixed, refresh-only set. Fix the CONCENTRATION_PARTS_PER_CUBIC_METER comment: HA still ships it (as a deprecated alias), so it is kept for backwards compatibility rather than being ZHA-only.
The summary now lists, per enum, added/removed members and value changes (NAME: old -> new), plus 'added/removed enum' for whole enums and a 'docstring/comment updates' note when only prose changed. This flows into the workflow's PR body, so reviewers see what a sync did at a glance rather than a coarse 'updated device classes'. Also drops the stale 'run ruff format' line now that the workflow has no format step.
The step uses the default shell (bash -e, no pipefail), so piping the sync tool into tee masked its exit code — a crash or one of the tool's loud LookupError guards would have gone green and could close the open sync PR with an empty diff. Enable pipefail so the pipeline fails.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## dev #812 +/- ##
=======================================
Coverage 97.29% 97.29%
=======================================
Files 55 55
Lines 10933 10942 +9
=======================================
+ Hits 10637 10646 +9
Misses 296 296 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces an automated way to keep ZHA’s local, near-1:1 mirrors of Home Assistant unit enums and device-class/mode enums in sync, replacing the prior manual drift-detection script and adding a scheduled GitHub workflow to open/update a dedicated sync PR.
Changes:
- Add
tools/sync_constants.pyto copy mirrored enums verbatim from an installed Home Assistant checkout and provide a--checkdrift mode. - Add a scheduled workflow that installs HA
dev, runs the sync tool, and opens/updates a single PR onsync/device-classes. - Update
zha/units.pyto include newUnitOfDensity/UnitOfRatioenums and move mirrored module-level constants into a dedicated backwards-compatibility section; remove the oldtools/compare_constants.pyand drophomeassistantfrom thetestingextra.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
zha/units.py |
Adds new unit enums and consolidates mirrored module-level constants into a marked backcompat section. |
tools/sync_constants.py |
New sync tool that replaces mirrored enum definitions from installed Home Assistant source and summarizes changes. |
tools/README.md |
Documents how to run the new sync tool and the intended workflow behavior. |
tools/compare_constants.py |
Removes the prior manual drift-detection script. |
pyproject.toml |
Drops homeassistant from the testing optional extra. |
.github/workflows/sync-device-classes.yml |
New scheduled workflow to run syncing against HA dev and maintain a single sync PR. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Request '>=3.14.2' for the venv so it can't resolve to a 3.14.x older than Home Assistant's minimum (uv accepts the lower-bound specifier). - Split the app user-id lookup across two lines with an intermediate variable. The nested quotes worked (bash re-parses inside $(...)), but the flatter form is clearer and avoids the ambiguity a reviewer flagged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DRAFT. Other thoughts:
homeassistant+uv.lockchange?compare_constanty.pyscript?Summary
Automates keeping ZHA's local, near-1:1 copies of Home Assistant enums in sync with HA. ZHA mirrors a set of HA enums — the unit enums in
zha/units.pyand the device-class / mode enums underzha/application/platforms/{binary_sensor,sensor,number}/device_class.py— including their docstrings and comments, which drift silently whenever HA changes them. The previoustools/compare_constants.py(from #810) could only detect drift for a subset, and only when run by hand.This replaces that script with a syncing tool plus a scheduled workflow that copies the enums verbatim from Home Assistant and opens a pull request with the result.
How it works
tools/sync_constants.pycopies each mirrored enum verbatim from the installedhomeassistantpackage viainspect.getsource(so docstrings and in-class comments come along), using AST-located line edits. It parses ZHA's files as text and never imports them, so it only needshomeassistantinstalled.UnitOf*) — existing ones refreshed, missing ones appended, and ones HA has removed deleted (ZHA's unit enums exist solely to mirror HA). If HA merely relocates an enum out ofhomeassistant.constwhile still exposing it, the tool fails loudly rather than deleting ZHA's copy.python -m tools.sync_constants --checkis a dry run (exits 1 on drift)..github/workflows/sync-device-classes.ymlruns daily (and on demand):home-assistant/core@dev, installs HA (Python 3.14), runs the tool, and opens/updates a single pull request on a fixedsync/device-classesbranch (each run updates the same PR).NAME: old -> newvalue changes, whole added/removed enums, and a note when only docstrings/comments changed.Backwards-compatibility constants
HA now derives constants like
PERCENTAGEandCONCENTRATION_*from the newUnitOfDensity/UnitOfRatioenums. This PR adds those two enums tozha.unitsand moves the module-level constants ZHA exposes into a single hand-maintained "backwards-compatibility" section at the end of the file, deriving them the same way HA does — values are unchanged, so existing imports keep working. The tool never rewrites that section; only enums are synced.Other changes
homeassistantis dropped from thetestingextras (the workflow installs HA itself), shrinkinguv.lockconsiderably.Setup required before enabling
The workflow needs a GitHub App with contents + pull-requests write, exposed as the
SYNC_APP_IDrepository variable andSYNC_APP_PRIVATE_KEYsecret. An App-authored PR is what lets CI run on the sync PR.After merge
The first workflow run will open a
sync/device-classesPR with the device-class docstring/comment updates accumulated on HAdev(intentionally not included here). Drift in the hand-maintained backwards-compat constants section is not auto-detected — that stays a manual update.