SwitchDeviceClass only ever supported 'outlet'/'switch', not 'lock'. switch.py passes desc.device_class straight to SwitchDeviceClass(...), so any board with these hrefs raised ValueError during switch platform setup and lost every switch entity for the device, not just the lock ones (issue #349, TP2X_WATERPURIFIER_20K). KIDS_LOCK_GENERIC/_VS_FALLBACK dodged this same bug (issues #181/#183) by moving to a read-only BinarySensorDesc, but the water-purifier and cooktop locks are genuinely writable, so they stay SwitchDesc and just drop the invalid device_class (with an mdi:lock icon standing in for the one entity_category=config gave them for free). Added a registry-wide test that instantiates SwitchDeviceClass for every SwitchDesc.device_class across every by_type registry, so a future capability can't reintroduce the same crash.
46 lines
1.8 KiB
Python
46 lines
1.8 KiB
Python
"""Guards against a SwitchDesc.device_class HA's SwitchDeviceClass doesn't
|
|
recognize -- switch.py passes it straight to SwitchDeviceClass(...), and an
|
|
invalid value raises out of the whole switch platform's async_setup_entry,
|
|
taking down every switch entity for the device, not just the offending one
|
|
(issue #349: water_purifier.py's lock switches used device_class='lock',
|
|
which SwitchDeviceClass only ever supported as 'outlet'/'switch').
|
|
|
|
Scans every by_type registry's declared capabilities rather than a specific
|
|
fixture, so a new capability introducing the same mistake fails here instead
|
|
of only surfacing as a live crash report.
|
|
"""
|
|
|
|
import importlib
|
|
import pkgutil
|
|
|
|
from homeassistant.components.switch import SwitchDeviceClass
|
|
|
|
from custom_components.localthings.registry import by_type
|
|
from custom_components.localthings.registry.entities import SwitchDesc
|
|
|
|
|
|
def _all_registries():
|
|
for mod_info in pkgutil.iter_modules(by_type.__path__):
|
|
if mod_info.name.startswith("_"):
|
|
continue
|
|
mod = importlib.import_module(
|
|
f"custom_components.localthings.registry.by_type.{mod_info.name}"
|
|
)
|
|
reg = getattr(mod, "REGISTRY", None)
|
|
if reg is not None:
|
|
yield reg
|
|
|
|
|
|
def test_every_switchdesc_device_class_is_valid_for_ha():
|
|
bad = []
|
|
for reg in _all_registries():
|
|
caps = [c for cs in reg.capabilities.values() for c in cs] + list(reg.pattern_capabilities)
|
|
for cap in caps:
|
|
for entity in cap.entities:
|
|
if isinstance(entity, SwitchDesc) and entity.device_class is not None:
|
|
try:
|
|
SwitchDeviceClass(entity.device_class)
|
|
except ValueError:
|
|
bad.append((reg.name, entity.key, entity.device_class))
|
|
assert bad == []
|