Skip to content

Keep destination unsupported marks when cloning an attribute cache - #1887

Open
cpruijsen wants to merge 4 commits into
zigpy:devfrom
cpruijsen:fix/issue-1875
Open

cpruijsen wants to merge 4 commits into
zigpy:devfrom
cpruijsen:fix/issue-1875

Conversation

@cpruijsen

Copy link
Copy Markdown

Restoring the attribute cache from zigbee.db clears unsupported-attribute marks that a quirk
declared. The quirk knows the device does not support an attribute, and a stale cached value from
before the quirk applied overwrites that knowledge, so zigpy resumes polling an attribute the device
will never answer.

The database is a record of what was read previously. A quirk is a statement about the device itself,
so where the two disagree the quirk is the better authority: the cached value is evidence the
attribute was readable under a different set of assumptions.

zigpy/appdb.py now leaves an attribute alone during restore when the cluster already marks it
unsupported, and zigpy/zcl/helpers.py carries the supporting change. Attributes with no such mark
restore exactly as before, so the cache still does its job for everything the quirk says nothing
about.

The test builds a quirked cluster declaring an attribute unsupported, seeds the DB with a stale value
for it, and asserts the mark survives the restore.

Fixes #1875

The attribute cache restore in load() cleared every quirked cluster's
cache before replaying persisted rows, erasing unsupported-attribute
declarations made by the active quirk. A stale SUCCESS row for such an
attribute then restored the value and the row kept being re-persisted.

Clear only the cached values (keep the declared unsupported marks) and
skip restoring SUCCESS rows for attributes the cluster has declared
unsupported, so stale rows can be rewritten as unsupported instead of
resurrecting the attribute. Fixes zigpy#1875.
Quirk cluster replacement copies the restored cache onto a newly
instantiated cluster, which discarded unsupported-attribute declarations
made in the replacement's __init__. Union those marks into the clone so
a stale SUCCESS row cannot resurrect the attribute on restore.
@TheJulianJES
TheJulianJES self-requested a review September 14, 2026 03:21
@codecov

codecov Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.50%. Comparing base (512f3cf) to head (0ba885f).

Additional details and impacted files
@@           Coverage Diff           @@
##              dev    #1887   +/-   ##
=======================================
  Coverage   99.50%   99.50%           
=======================================
  Files          59       59           
  Lines       12415    12418    +3     
=======================================
+ Hits        12353    12356    +3     
  Misses         62       62           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

@puddly

puddly commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

This is a bit of a tricky case. I think calling self.add_unsupported_attribute within __init__ to prevent an entity from being created accomplishes its goal but indirectly: it pollutes the (already fake) ZCL database state with runtime modifications to mimic real device communication.

This isn't a common pattern. I think the better approach would be to use prevent_default_entity_creation, which is the dedicated API to accomplish exactly this.

@cpruijsen

Copy link
Copy Markdown
Author

Pushed 70e5357 for the ruff-format failure.

On prevent_default_entity_creation: it lives in zha-device-handlers (zhaquirks/builder/builder.py), not zigpy, so the test here cannot use it. Agreed it is the right tool for suppressing an entity, and the PJ-1203A quirk in the issue would be better served by it.

The zigpy-level bug looks separate from entity creation. is_attribute_unsupported() is zigpy's own public API, and its answer flips across a restart whenever a stale row exists for that attribute. The quirk's __init__ declaration is erased by the restore regardless of what any consumer does with the answer, and that is what this PR changes.

So the question is whether zigpy wants add_unsupported_attribute() at instantiation to survive a database restore. If not, and quirks should express this only through prevent_default_entity_creation, I am happy to close this and the issue. If yes, the test can set the mark on the cluster directly rather than overriding __init__.

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.

Attribute cache restore from zigbee.db clears unsupported-attribute marks declared by quirks

2 participants