Fix prevent_default_entity_creation ignoring cluster_type#716
Fix prevent_default_entity_creation ignoring cluster_type#716TheJulianJES wants to merge 2 commits into
prevent_default_entity_creation ignoring cluster_type#716Conversation
zigpy-review-bot
left a comment
There was a problem hiding this comment.
Confirmed the change does what the title says: the predicate now also matches cluster_type, so a prevent_default_entity_creation(cluster_id=X) no longer collapses Server and Client cluster handlers together. The fix is one line and the parametrised test (test_quirks_v2_prevent_default_entities_cluster_type[None|Server|Client]) is well-shaped: it pins both branches of the new condition and asserts the QuirkBuilder's None -> Server normalisation. Runs locally on Py 3.14 (3 passed). Mypy diff is zero — the touched expression stays trivially typed.
CI failures are not unrelated — they're the behaviour change this PR caused
The two failing snapshots (tests/test_discover.py::test_devices_from_files[ewelink-snzb-04p.json] and [legrand-light-switch-with-neutral.json]) are direct consequences of the new strictness. Reproduced locally on this branch.
Walkthrough for ewelink-snzb-04p:
- The SNZB-04P signature has
output_clusters=[0x0003, 0x0006, 0x0019]— OnOff is on the client side. zhaquirks/sonoff/snzb04p.pycalls.prevent_default_entity_creation(endpoint_id=1, cluster_id=OnOff.cluster_id)with nocluster_type.QuirkBuilder.prevent_default_entity_creationnormalises that tocluster_type=ClusterType.Server(in zigpyquirks/v2/__init__.py).- Before this PR:
meta.cluster_id == handler.cluster_idmatched theOnOffClientClusterHandler(id 6) and the defaultOpeningbinary_sensor on1-6was removed. - After this PR:
meta.cluster_type == Serverno longer matches the client handler, soOpeningreappears in the snapshot — which is why the diff shows an extra'class_name': 'OnOffClientClusterHandler'/'unique_id': '...-1-6'entry.
Same mechanism applies to the Legrand light switch with neutral quirk (.prevent_default_entity_creation(endpoint_id=1, cluster_id=OnOff.cluster_id, function=lambda entity: entity.device_class == "opening")).
So the PR is correct in isolation, but it silently changes the meaning of every existing prevent_default_entity_creation(cluster_id=...) call site in zha-device-handlers that targeted a client cluster — which you already flagged in the description. Marking this Draft is right: it shouldn't merge until either
- those quirks are updated to pass
cluster_type=ClusterType.Client(and the snapshots regen'd), or - the diagnostics device files are regen'd if the new default behaviour is genuinely what's intended for those devices.
The quirks I found that pass cluster_id without cluster_type and may need the same audit:
zhaquirks/sonoff/snzb04p.py(OnOff, client) — affects this PR's CIzhaquirks/legrand/switch.py(OnOff, client) — affects this PR's CIzhaquirks/legrand/connected_outled.py,dimmer.py,micromodule.py,shutter.py,wirelessswitch.py,wirelessshutter.py(BinaryInput, cluster 15 — these are input clusters on the affected endpoints, so server-side and unaffected, but worth a manual sanity-check while we're touching the surface)
Cross-PR: complementary with #763, not conflicting
Verified both at HEAD: #716's change is _is_entity_removed_by_quirk body, line 886 (adding cluster_type to the any(...) predicate inside the existing loop). #763's change is an early return on the function — if entity._from_quirks_v2: return False — placed several lines above the predicate loop. Different concerns, different lines. The fetched branches merge cleanly on zha/zigbee/device.py; the only conflict is the test-file imports both PRs added independently, trivial three-way resolution.
The two bugs are also semantically orthogonal: #763 stops prevent_default_entity_creation from clobbering v2 entities the same quirk just declared (intra-quirk false-positive); #716 stops a single rule's cluster filter from matching the wrong cluster side (cross-side false-positive). Either can land first; both should land.
Minor: dataclass-side defensive shape
PreventDefaultEntityCreationMetadata types cluster_type: ClusterType | None. The QuirkBuilder normalises None -> Server when cluster_id is set, but the dataclass is attrs.define(frozen=True) and a caller instantiating it directly (or future builder paths) could leave both cluster_id=X and cluster_type=None. In that case the new check handler.cluster.cluster_type == None would never match and the rule would silently match nothing.
Not worth blocking on — the only constructor today is QuirkBuilder.prevent_default_entity_creation, which always sets cluster_type. But a tiny defensiveness like
and (meta.cluster_type is None
or cluster_handler.cluster.cluster_type == meta.cluster_type)would make the predicate read as "match the type when one is specified, otherwise match either" and keep direct-instantiation paths in the metadata's documented | None shape. Up to you whether that's worth it; if you'd rather treat None as invalid for cluster_type once cluster_id is set, fixing it in the zigpy dataclass (__attrs_post_init__ or removing the | None) would be cleaner.
Proposed change
This fixes an issue where
prevent_default_entity_creationignored acluster_typewhen provided.Note: The zigpy metadata defaults to
cluster_type=.SERVR, so this changes the behavior wherecluster_typeis currently omitted. We'll need to re-evaluate all quirks usage of this to see if a client and/or server entity should be hidden instead.Additional information
Fixes:
prevent_default_entity_creationcluster_typedoesn't work forOnOffopening entity in ZHA zha-device-handlers#4284