Files
core/script/hassfest/device_classes.py

92 lines
3.3 KiB
Python

"""Generate the device_classes.json file."""
import json
import re
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
from homeassistant.components.button import ButtonDeviceClass
from homeassistant.components.cover.const import CoverDeviceClass
from homeassistant.components.event import EventDeviceClass
from homeassistant.components.humidifier import HumidifierDeviceClass
from homeassistant.components.image_processing import ImageProcessingDeviceClass
from homeassistant.components.infrared.entity import InfraredDeviceClass
from homeassistant.components.media_player import MediaPlayerDeviceClass
from homeassistant.components.number.const import NumberDeviceClass
from homeassistant.components.sensor.const import SensorDeviceClass
from homeassistant.components.switch import SwitchDeviceClass
from homeassistant.components.update import UpdateDeviceClass
from homeassistant.components.valve.const import ValveDeviceClass
from .model import Config, Integration
PATH = "homeassistant/generated/device_classes.json"
DEVICE_CLASS_ENUMS = {
"binary_sensor": BinarySensorDeviceClass,
"button": ButtonDeviceClass,
"cover": CoverDeviceClass,
"event": EventDeviceClass,
"humidifier": HumidifierDeviceClass,
"image_processing": ImageProcessingDeviceClass,
"infrared": InfraredDeviceClass,
"media_player": MediaPlayerDeviceClass,
"number": NumberDeviceClass,
"sensor": SensorDeviceClass,
"switch": SwitchDeviceClass,
"update": UpdateDeviceClass,
"valve": ValveDeviceClass,
}
DEVICE_CLASS_ENUM_DEFINITION = re.compile(
r"^class \w*DeviceClass\(StrEnum\)", re.MULTILINE
)
def find_undeclared_domains(integrations: dict[str, Integration]) -> set[str]:
"""Return entity domains defining a device class enum but missing above."""
return {
domain
for domain, integration in integrations.items()
if domain not in DEVICE_CLASS_ENUMS
and integration.manifest.get("integration_type") == "entity"
and any(
DEVICE_CLASS_ENUM_DEFINITION.search(path.read_text(encoding="utf-8"))
for path in integration.path.rglob("*.py")
)
}
def _generate() -> str:
"""Generate the device class data."""
device_classes = {
domain: sorted(device_class.value for device_class in enum)
for domain, enum in sorted(DEVICE_CLASS_ENUMS.items())
}
return json.dumps({"device_classes": device_classes}, indent=2)
def validate(integrations: dict[str, Integration], config: Config) -> None:
"""Validate device_classes.json."""
if undeclared := find_undeclared_domains(integrations):
config.add_error(
"device_classes",
f"Add {', '.join(sorted(undeclared))} to DEVICE_CLASS_ENUMS in"
" script/hassfest/device_classes.py",
)
path = config.root / PATH
config.cache["device_classes"] = content = _generate()
if path.read_text() != content + "\n":
config.add_error(
"device_classes",
"File device_classes.json is not up to date. Run python3 -m script.hassfest",
fixable=True,
)
def generate(integrations: dict[str, Integration], config: Config) -> None:
"""Generate device_classes.json."""
path = config.root / PATH
path.write_text(f"{config.cache['device_classes']}\n")