Replace ZHA quirk class matching with quirk ID matching (#102482)

* Use fixed quirk IDs for matching instead of quirk class

* Change tests for quirk id (WIP)

* Do not default `quirk_id` to `quirk_class`

* Implement test for checking if quirk ID exists

* Change `quirk_id` for test slightly (underscore instead of dot)
This commit is contained in:
TheJulianJES 2023-10-24 23:18:10 +02:00 committed by GitHub
parent 5ee14f7f7d
commit fd8fdba7e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 80 deletions

View File

@ -48,6 +48,7 @@ ATTR_POWER_SOURCE = "power_source"
ATTR_PROFILE_ID = "profile_id" ATTR_PROFILE_ID = "profile_id"
ATTR_QUIRK_APPLIED = "quirk_applied" ATTR_QUIRK_APPLIED = "quirk_applied"
ATTR_QUIRK_CLASS = "quirk_class" ATTR_QUIRK_CLASS = "quirk_class"
ATTR_QUIRK_ID = "quirk_id"
ATTR_ROUTES = "routes" ATTR_ROUTES = "routes"
ATTR_RSSI = "rssi" ATTR_RSSI = "rssi"
ATTR_SIGNATURE = "signature" ATTR_SIGNATURE = "signature"

View File

@ -59,6 +59,7 @@ from .const import (
ATTR_POWER_SOURCE, ATTR_POWER_SOURCE,
ATTR_QUIRK_APPLIED, ATTR_QUIRK_APPLIED,
ATTR_QUIRK_CLASS, ATTR_QUIRK_CLASS,
ATTR_QUIRK_ID,
ATTR_ROUTES, ATTR_ROUTES,
ATTR_RSSI, ATTR_RSSI,
ATTR_SIGNATURE, ATTR_SIGNATURE,
@ -135,6 +136,7 @@ class ZHADevice(LogMixin):
f"{self._zigpy_device.__class__.__module__}." f"{self._zigpy_device.__class__.__module__}."
f"{self._zigpy_device.__class__.__name__}" f"{self._zigpy_device.__class__.__name__}"
) )
self.quirk_id = getattr(self._zigpy_device, ATTR_QUIRK_ID, None)
if self.is_mains_powered: if self.is_mains_powered:
self.consider_unavailable_time = async_get_zha_config_value( self.consider_unavailable_time = async_get_zha_config_value(
@ -537,6 +539,7 @@ class ZHADevice(LogMixin):
ATTR_NAME: self.name or ieee, ATTR_NAME: self.name or ieee,
ATTR_QUIRK_APPLIED: self.quirk_applied, ATTR_QUIRK_APPLIED: self.quirk_applied,
ATTR_QUIRK_CLASS: self.quirk_class, ATTR_QUIRK_CLASS: self.quirk_class,
ATTR_QUIRK_ID: self.quirk_id,
ATTR_MANUFACTURER_CODE: self.manufacturer_code, ATTR_MANUFACTURER_CODE: self.manufacturer_code,
ATTR_POWER_SOURCE: self.power_source, ATTR_POWER_SOURCE: self.power_source,
ATTR_LQI: self.lqi, ATTR_LQI: self.lqi,

View File

@ -122,7 +122,7 @@ class ProbeEndpoint:
endpoint.device.manufacturer, endpoint.device.manufacturer,
endpoint.device.model, endpoint.device.model,
cluster_handlers, cluster_handlers,
endpoint.device.quirk_class, endpoint.device.quirk_id,
) )
if platform_entity_class is None: if platform_entity_class is None:
return return
@ -181,7 +181,7 @@ class ProbeEndpoint:
endpoint.device.manufacturer, endpoint.device.manufacturer,
endpoint.device.model, endpoint.device.model,
cluster_handler_list, cluster_handler_list,
endpoint.device.quirk_class, endpoint.device.quirk_id,
) )
if entity_class is None: if entity_class is None:
return return
@ -226,14 +226,14 @@ class ProbeEndpoint:
endpoint.device.manufacturer, endpoint.device.manufacturer,
endpoint.device.model, endpoint.device.model,
list(endpoint.all_cluster_handlers.values()), list(endpoint.all_cluster_handlers.values()),
endpoint.device.quirk_class, endpoint.device.quirk_id,
) )
else: else:
matches, claimed = zha_regs.ZHA_ENTITIES.get_multi_entity( matches, claimed = zha_regs.ZHA_ENTITIES.get_multi_entity(
endpoint.device.manufacturer, endpoint.device.manufacturer,
endpoint.device.model, endpoint.device.model,
endpoint.unclaimed_cluster_handlers(), endpoint.unclaimed_cluster_handlers(),
endpoint.device.quirk_class, endpoint.device.quirk_id,
) )
endpoint.claim_cluster_handlers(claimed) endpoint.claim_cluster_handlers(claimed)

View File

@ -147,7 +147,7 @@ class MatchRule:
aux_cluster_handlers: frozenset[str] | Callable = attr.ib( aux_cluster_handlers: frozenset[str] | Callable = attr.ib(
factory=_get_empty_frozenset, converter=set_or_callable factory=_get_empty_frozenset, converter=set_or_callable
) )
quirk_classes: frozenset[str] | Callable = attr.ib( quirk_ids: frozenset[str] | Callable = attr.ib(
factory=_get_empty_frozenset, converter=set_or_callable factory=_get_empty_frozenset, converter=set_or_callable
) )
@ -165,10 +165,8 @@ class MatchRule:
multiple cluster handlers a better priority over rules matching a single cluster handler. multiple cluster handlers a better priority over rules matching a single cluster handler.
""" """
weight = 0 weight = 0
if self.quirk_classes: if self.quirk_ids:
weight += 501 - ( weight += 501 - (1 if callable(self.quirk_ids) else len(self.quirk_ids))
1 if callable(self.quirk_classes) else len(self.quirk_classes)
)
if self.models: if self.models:
weight += 401 - (1 if callable(self.models) else len(self.models)) weight += 401 - (1 if callable(self.models) else len(self.models))
@ -204,19 +202,31 @@ class MatchRule:
return claimed return claimed
def strict_matched( def strict_matched(
self, manufacturer: str, model: str, cluster_handlers: list, quirk_class: str self,
manufacturer: str,
model: str,
cluster_handlers: list,
quirk_id: str | None,
) -> bool: ) -> bool:
"""Return True if this device matches the criteria.""" """Return True if this device matches the criteria."""
return all(self._matched(manufacturer, model, cluster_handlers, quirk_class)) return all(self._matched(manufacturer, model, cluster_handlers, quirk_id))
def loose_matched( def loose_matched(
self, manufacturer: str, model: str, cluster_handlers: list, quirk_class: str self,
manufacturer: str,
model: str,
cluster_handlers: list,
quirk_id: str | None,
) -> bool: ) -> bool:
"""Return True if this device matches the criteria.""" """Return True if this device matches the criteria."""
return any(self._matched(manufacturer, model, cluster_handlers, quirk_class)) return any(self._matched(manufacturer, model, cluster_handlers, quirk_id))
def _matched( def _matched(
self, manufacturer: str, model: str, cluster_handlers: list, quirk_class: str self,
manufacturer: str,
model: str,
cluster_handlers: list,
quirk_id: str | None,
) -> list: ) -> list:
"""Return a list of field matches.""" """Return a list of field matches."""
if not any(attr.asdict(self).values()): if not any(attr.asdict(self).values()):
@ -243,14 +253,11 @@ class MatchRule:
else: else:
matches.append(model in self.models) matches.append(model in self.models)
if self.quirk_classes: if self.quirk_ids and quirk_id:
if callable(self.quirk_classes): if callable(self.quirk_ids):
matches.append(self.quirk_classes(quirk_class)) matches.append(self.quirk_ids(quirk_id))
else: else:
matches.append( matches.append(quirk_id in self.quirk_ids)
quirk_class.split(".")[-2:]
in [x.split(".")[-2:] for x in self.quirk_classes]
)
return matches return matches
@ -292,13 +299,13 @@ class ZHAEntityRegistry:
manufacturer: str, manufacturer: str,
model: str, model: str,
cluster_handlers: list[ClusterHandler], cluster_handlers: list[ClusterHandler],
quirk_class: str, quirk_id: str | None,
default: type[ZhaEntity] | None = None, default: type[ZhaEntity] | None = None,
) -> tuple[type[ZhaEntity] | None, list[ClusterHandler]]: ) -> tuple[type[ZhaEntity] | None, list[ClusterHandler]]:
"""Match a ZHA ClusterHandler to a ZHA Entity class.""" """Match a ZHA ClusterHandler to a ZHA Entity class."""
matches = self._strict_registry[component] matches = self._strict_registry[component]
for match in sorted(matches, key=WEIGHT_ATTR, reverse=True): for match in sorted(matches, key=WEIGHT_ATTR, reverse=True):
if match.strict_matched(manufacturer, model, cluster_handlers, quirk_class): if match.strict_matched(manufacturer, model, cluster_handlers, quirk_id):
claimed = match.claim_cluster_handlers(cluster_handlers) claimed = match.claim_cluster_handlers(cluster_handlers)
return self._strict_registry[component][match], claimed return self._strict_registry[component][match], claimed
@ -309,7 +316,7 @@ class ZHAEntityRegistry:
manufacturer: str, manufacturer: str,
model: str, model: str,
cluster_handlers: list[ClusterHandler], cluster_handlers: list[ClusterHandler],
quirk_class: str, quirk_id: str | None,
) -> tuple[ ) -> tuple[
dict[Platform, list[EntityClassAndClusterHandlers]], list[ClusterHandler] dict[Platform, list[EntityClassAndClusterHandlers]], list[ClusterHandler]
]: ]:
@ -323,7 +330,7 @@ class ZHAEntityRegistry:
sorted_matches = sorted(matches, key=WEIGHT_ATTR, reverse=True) sorted_matches = sorted(matches, key=WEIGHT_ATTR, reverse=True)
for match in sorted_matches: for match in sorted_matches:
if match.strict_matched( if match.strict_matched(
manufacturer, model, cluster_handlers, quirk_class manufacturer, model, cluster_handlers, quirk_id
): ):
claimed = match.claim_cluster_handlers(cluster_handlers) claimed = match.claim_cluster_handlers(cluster_handlers)
for ent_class in stop_match_groups[stop_match_grp][match]: for ent_class in stop_match_groups[stop_match_grp][match]:
@ -342,7 +349,7 @@ class ZHAEntityRegistry:
manufacturer: str, manufacturer: str,
model: str, model: str,
cluster_handlers: list[ClusterHandler], cluster_handlers: list[ClusterHandler],
quirk_class: str, quirk_id: str | None,
) -> tuple[ ) -> tuple[
dict[Platform, list[EntityClassAndClusterHandlers]], list[ClusterHandler] dict[Platform, list[EntityClassAndClusterHandlers]], list[ClusterHandler]
]: ]:
@ -359,7 +366,7 @@ class ZHAEntityRegistry:
sorted_matches = sorted(matches, key=WEIGHT_ATTR, reverse=True) sorted_matches = sorted(matches, key=WEIGHT_ATTR, reverse=True)
for match in sorted_matches: for match in sorted_matches:
if match.strict_matched( if match.strict_matched(
manufacturer, model, cluster_handlers, quirk_class manufacturer, model, cluster_handlers, quirk_id
): ):
claimed = match.claim_cluster_handlers(cluster_handlers) claimed = match.claim_cluster_handlers(cluster_handlers)
for ent_class in stop_match_groups[stop_match_grp][match]: for ent_class in stop_match_groups[stop_match_grp][match]:
@ -385,7 +392,7 @@ class ZHAEntityRegistry:
manufacturers: Callable | set[str] | str | None = None, manufacturers: Callable | set[str] | str | None = None,
models: Callable | set[str] | str | None = None, models: Callable | set[str] | str | None = None,
aux_cluster_handlers: Callable | set[str] | str | None = None, aux_cluster_handlers: Callable | set[str] | str | None = None,
quirk_classes: set[str] | str | None = None, quirk_ids: set[str] | str | None = None,
) -> Callable[[_ZhaEntityT], _ZhaEntityT]: ) -> Callable[[_ZhaEntityT], _ZhaEntityT]:
"""Decorate a strict match rule.""" """Decorate a strict match rule."""
@ -395,7 +402,7 @@ class ZHAEntityRegistry:
manufacturers, manufacturers,
models, models,
aux_cluster_handlers, aux_cluster_handlers,
quirk_classes, quirk_ids,
) )
def decorator(zha_ent: _ZhaEntityT) -> _ZhaEntityT: def decorator(zha_ent: _ZhaEntityT) -> _ZhaEntityT:
@ -417,7 +424,7 @@ class ZHAEntityRegistry:
models: Callable | set[str] | str | None = None, models: Callable | set[str] | str | None = None,
aux_cluster_handlers: Callable | set[str] | str | None = None, aux_cluster_handlers: Callable | set[str] | str | None = None,
stop_on_match_group: int | str | None = None, stop_on_match_group: int | str | None = None,
quirk_classes: set[str] | str | None = None, quirk_ids: set[str] | str | None = None,
) -> Callable[[_ZhaEntityT], _ZhaEntityT]: ) -> Callable[[_ZhaEntityT], _ZhaEntityT]:
"""Decorate a loose match rule.""" """Decorate a loose match rule."""
@ -427,7 +434,7 @@ class ZHAEntityRegistry:
manufacturers, manufacturers,
models, models,
aux_cluster_handlers, aux_cluster_handlers,
quirk_classes, quirk_ids,
) )
def decorator(zha_entity: _ZhaEntityT) -> _ZhaEntityT: def decorator(zha_entity: _ZhaEntityT) -> _ZhaEntityT:
@ -452,7 +459,7 @@ class ZHAEntityRegistry:
models: Callable | set[str] | str | None = None, models: Callable | set[str] | str | None = None,
aux_cluster_handlers: Callable | set[str] | str | None = None, aux_cluster_handlers: Callable | set[str] | str | None = None,
stop_on_match_group: int | str | None = None, stop_on_match_group: int | str | None = None,
quirk_classes: set[str] | str | None = None, quirk_ids: set[str] | str | None = None,
) -> Callable[[_ZhaEntityT], _ZhaEntityT]: ) -> Callable[[_ZhaEntityT], _ZhaEntityT]:
"""Decorate a loose match rule.""" """Decorate a loose match rule."""
@ -462,7 +469,7 @@ class ZHAEntityRegistry:
manufacturers, manufacturers,
models, models,
aux_cluster_handlers, aux_cluster_handlers,
quirk_classes, quirk_ids,
) )
def decorator(zha_entity: _ZhaEntityT) -> _ZhaEntityT: def decorator(zha_entity: _ZhaEntityT) -> _ZhaEntityT:

View File

@ -1,15 +1,14 @@
"""Test ZHA registries.""" """Test ZHA registries."""
from __future__ import annotations from __future__ import annotations
import importlib
import inspect
import typing import typing
from unittest import mock from unittest import mock
import pytest import pytest
import zhaquirks import zigpy.quirks as zigpy_quirks
from homeassistant.components.zha.binary_sensor import IASZone from homeassistant.components.zha.binary_sensor import IASZone
from homeassistant.components.zha.core.const import ATTR_QUIRK_ID
import homeassistant.components.zha.core.registries as registries import homeassistant.components.zha.core.registries as registries
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -19,7 +18,7 @@ if typing.TYPE_CHECKING:
MANUFACTURER = "mock manufacturer" MANUFACTURER = "mock manufacturer"
MODEL = "mock model" MODEL = "mock model"
QUIRK_CLASS = "mock.test.quirk.class" QUIRK_CLASS = "mock.test.quirk.class"
QUIRK_CLASS_SHORT = "quirk.class" QUIRK_ID = "quirk_id"
@pytest.fixture @pytest.fixture
@ -29,6 +28,7 @@ def zha_device():
dev.manufacturer = MANUFACTURER dev.manufacturer = MANUFACTURER
dev.model = MODEL dev.model = MODEL
dev.quirk_class = QUIRK_CLASS dev.quirk_class = QUIRK_CLASS
dev.quirk_id = QUIRK_ID
return dev return dev
@ -107,17 +107,17 @@ def cluster_handlers(cluster_handler):
), ),
False, False,
), ),
(registries.MatchRule(quirk_classes=QUIRK_CLASS), True), (registries.MatchRule(quirk_ids=QUIRK_ID), True),
(registries.MatchRule(quirk_classes="no match"), False), (registries.MatchRule(quirk_ids="no match"), False),
( (
registries.MatchRule( registries.MatchRule(
quirk_classes=QUIRK_CLASS, aux_cluster_handlers="aux_cluster_handler" quirk_ids=QUIRK_ID, aux_cluster_handlers="aux_cluster_handler"
), ),
True, True,
), ),
( (
registries.MatchRule( registries.MatchRule(
quirk_classes="no match", aux_cluster_handlers="aux_cluster_handler" quirk_ids="no match", aux_cluster_handlers="aux_cluster_handler"
), ),
False, False,
), ),
@ -128,7 +128,7 @@ def cluster_handlers(cluster_handler):
cluster_handler_names={"on_off", "level"}, cluster_handler_names={"on_off", "level"},
manufacturers=MANUFACTURER, manufacturers=MANUFACTURER,
models=MODEL, models=MODEL,
quirk_classes=QUIRK_CLASS, quirk_ids=QUIRK_ID,
), ),
True, True,
), ),
@ -187,33 +187,31 @@ def cluster_handlers(cluster_handler):
( (
registries.MatchRule( registries.MatchRule(
cluster_handler_names="on_off", cluster_handler_names="on_off",
quirk_classes={"random quirk", QUIRK_CLASS}, quirk_ids={"random quirk", QUIRK_ID},
), ),
True, True,
), ),
( (
registries.MatchRule( registries.MatchRule(
cluster_handler_names="on_off", cluster_handler_names="on_off",
quirk_classes={"random quirk", "another quirk"}, quirk_ids={"random quirk", "another quirk"},
), ),
False, False,
), ),
( (
registries.MatchRule( registries.MatchRule(
cluster_handler_names="on_off", quirk_classes=lambda x: x == QUIRK_CLASS cluster_handler_names="on_off", quirk_ids=lambda x: x == QUIRK_ID
), ),
True, True,
), ),
( (
registries.MatchRule( registries.MatchRule(
cluster_handler_names="on_off", quirk_classes=lambda x: x != QUIRK_CLASS cluster_handler_names="on_off", quirk_ids=lambda x: x != QUIRK_ID
), ),
False, False,
), ),
( (
registries.MatchRule( registries.MatchRule(cluster_handler_names="on_off", quirk_ids=QUIRK_ID),
cluster_handler_names="on_off", quirk_classes=QUIRK_CLASS_SHORT
),
True, True,
), ),
], ],
@ -221,8 +219,7 @@ def cluster_handlers(cluster_handler):
def test_registry_matching(rule, matched, cluster_handlers) -> None: def test_registry_matching(rule, matched, cluster_handlers) -> None:
"""Test strict rule matching.""" """Test strict rule matching."""
assert ( assert (
rule.strict_matched(MANUFACTURER, MODEL, cluster_handlers, QUIRK_CLASS) rule.strict_matched(MANUFACTURER, MODEL, cluster_handlers, QUIRK_ID) is matched
is matched
) )
@ -314,8 +311,8 @@ def test_registry_matching(rule, matched, cluster_handlers) -> None:
(registries.MatchRule(manufacturers=MANUFACTURER), True), (registries.MatchRule(manufacturers=MANUFACTURER), True),
(registries.MatchRule(models=MODEL), True), (registries.MatchRule(models=MODEL), True),
(registries.MatchRule(models="no match"), False), (registries.MatchRule(models="no match"), False),
(registries.MatchRule(quirk_classes=QUIRK_CLASS), True), (registries.MatchRule(quirk_ids=QUIRK_ID), True),
(registries.MatchRule(quirk_classes="no match"), False), (registries.MatchRule(quirk_ids="no match"), False),
# match everything # match everything
( (
registries.MatchRule( registries.MatchRule(
@ -323,7 +320,7 @@ def test_registry_matching(rule, matched, cluster_handlers) -> None:
cluster_handler_names={"on_off", "level"}, cluster_handler_names={"on_off", "level"},
manufacturers=MANUFACTURER, manufacturers=MANUFACTURER,
models=MODEL, models=MODEL,
quirk_classes=QUIRK_CLASS, quirk_ids=QUIRK_ID,
), ),
True, True,
), ),
@ -332,8 +329,7 @@ def test_registry_matching(rule, matched, cluster_handlers) -> None:
def test_registry_loose_matching(rule, matched, cluster_handlers) -> None: def test_registry_loose_matching(rule, matched, cluster_handlers) -> None:
"""Test loose rule matching.""" """Test loose rule matching."""
assert ( assert (
rule.loose_matched(MANUFACTURER, MODEL, cluster_handlers, QUIRK_CLASS) rule.loose_matched(MANUFACTURER, MODEL, cluster_handlers, QUIRK_ID) is matched
is matched
) )
@ -397,12 +393,12 @@ def entity_registry():
@pytest.mark.parametrize( @pytest.mark.parametrize(
("manufacturer", "model", "quirk_class", "match_name"), ("manufacturer", "model", "quirk_id", "match_name"),
( (
("random manufacturer", "random model", "random.class", "OnOff"), ("random manufacturer", "random model", "random.class", "OnOff"),
("random manufacturer", MODEL, "random.class", "OnOffModel"), ("random manufacturer", MODEL, "random.class", "OnOffModel"),
(MANUFACTURER, "random model", "random.class", "OnOffManufacturer"), (MANUFACTURER, "random model", "random.class", "OnOffManufacturer"),
("random manufacturer", "random model", QUIRK_CLASS, "OnOffQuirk"), ("random manufacturer", "random model", QUIRK_ID, "OnOffQuirk"),
(MANUFACTURER, MODEL, "random.class", "OnOffModelManufacturer"), (MANUFACTURER, MODEL, "random.class", "OnOffModelManufacturer"),
(MANUFACTURER, "some model", "random.class", "OnOffMultimodel"), (MANUFACTURER, "some model", "random.class", "OnOffMultimodel"),
), ),
@ -412,7 +408,7 @@ def test_weighted_match(
entity_registry: er.EntityRegistry, entity_registry: er.EntityRegistry,
manufacturer, manufacturer,
model, model,
quirk_class, quirk_id,
match_name, match_name,
) -> None: ) -> None:
"""Test weightedd match.""" """Test weightedd match."""
@ -453,7 +449,7 @@ def test_weighted_match(
pass pass
@entity_registry.strict_match( @entity_registry.strict_match(
s.component, cluster_handler_names="on_off", quirk_classes=QUIRK_CLASS s.component, cluster_handler_names="on_off", quirk_ids=QUIRK_ID
) )
class OnOffQuirk: class OnOffQuirk:
pass pass
@ -462,7 +458,7 @@ def test_weighted_match(
ch_level = cluster_handler("level", 8) ch_level = cluster_handler("level", 8)
match, claimed = entity_registry.get_entity( match, claimed = entity_registry.get_entity(
s.component, manufacturer, model, [ch_on_off, ch_level], quirk_class s.component, manufacturer, model, [ch_on_off, ch_level], quirk_id
) )
assert match.__name__ == match_name assert match.__name__ == match_name
@ -490,7 +486,7 @@ def test_multi_sensor_match(
"manufacturer", "manufacturer",
"model", "model",
cluster_handlers=[ch_se, ch_illuminati], cluster_handlers=[ch_se, ch_illuminati],
quirk_class="quirk_class", quirk_id="quirk_id",
) )
assert s.binary_sensor in match assert s.binary_sensor in match
@ -520,7 +516,7 @@ def test_multi_sensor_match(
"manufacturer", "manufacturer",
"model", "model",
cluster_handlers={ch_se, ch_illuminati}, cluster_handlers={ch_se, ch_illuminati},
quirk_class="quirk_class", quirk_id="quirk_id",
) )
assert s.binary_sensor in match assert s.binary_sensor in match
@ -554,18 +550,10 @@ def iter_all_rules() -> typing.Iterable[registries.MatchRule, list[type[ZhaEntit
def test_quirk_classes() -> None: def test_quirk_classes() -> None:
"""Make sure that quirk_classes in components matches are valid.""" """Make sure that all quirk IDs in components matches exist."""
def find_quirk_class(base_obj, quirk_mod, quirk_cls):
"""Find a specific quirk class."""
module = importlib.import_module(quirk_mod)
clss = dict(inspect.getmembers(module, inspect.isclass))
# Check quirk_cls in module classes
return quirk_cls in clss
def quirk_class_validator(value): def quirk_class_validator(value):
"""Validate quirk classes during self test.""" """Validate quirk IDs during self test."""
if callable(value): if callable(value):
# Callables cannot be tested # Callables cannot be tested
return return
@ -576,16 +564,22 @@ def test_quirk_classes() -> None:
quirk_class_validator(v) quirk_class_validator(v)
return return
quirk_tok = value.rsplit(".", 1) if value not in all_quirk_ids:
if len(quirk_tok) != 2: raise ValueError(f"Quirk ID '{value}' does not exist.")
# quirk_class is at least __module__.__class__
raise ValueError(f"Invalid quirk class : '{value}'")
if not find_quirk_class(zhaquirks, quirk_tok[0], quirk_tok[1]): # get all quirk ID from zigpy quirks registry
raise ValueError(f"Quirk class '{value}' does not exists.") all_quirk_ids = []
for manufacturer in zigpy_quirks._DEVICE_REGISTRY._registry.values():
for model_quirk_list in manufacturer.values():
for quirk in model_quirk_list:
quirk_id = getattr(quirk, ATTR_QUIRK_ID, None)
if quirk_id is not None and quirk_id not in all_quirk_ids:
all_quirk_ids.append(quirk_id)
del quirk, model_quirk_list, manufacturer
# validate all quirk IDs used in component match rules
for rule, _ in iter_all_rules(): for rule, _ in iter_all_rules():
quirk_class_validator(rule.quirk_classes) quirk_class_validator(rule.quirk_ids)
def test_entity_names() -> None: def test_entity_names() -> None: