mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
Add platform Entity classes to pylint plugin (#125737)
* Add platform Entity classes to pylint plugin * Fix violations * Fix violations * More * Allow component package with same name as a platform * One more
This commit is contained in:
parent
3dd6418160
commit
457f63cce0
@ -25,6 +25,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HuePresence(GenericZLLSensor, BinarySensorEntity):
|
||||
"""The presence sensor entity for a Hue motion sensor device."""
|
||||
|
||||
|
@ -305,6 +305,7 @@ def hass_to_hue_brightness(value):
|
||||
return max(1, round((value / 255) * 254))
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueLight(CoordinatorEntity, LightEntity):
|
||||
"""Representation of a Hue light."""
|
||||
|
||||
|
@ -32,10 +32,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
await bridge.sensor_manager.async_register_component("sensor", async_add_entities)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class GenericHueGaugeSensorEntity(GenericZLLSensor, SensorEntity):
|
||||
"""Parent class for all 'gauge' Hue device sensors."""
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueLightLevel(GenericHueGaugeSensorEntity):
|
||||
"""The light level sensor entity for a Hue motion sensor device."""
|
||||
|
||||
@ -71,6 +73,7 @@ class HueLightLevel(GenericHueGaugeSensorEntity):
|
||||
return attributes
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueTemperature(GenericHueGaugeSensorEntity):
|
||||
"""The temperature sensor entity for a Hue motion sensor device."""
|
||||
|
||||
@ -87,6 +90,7 @@ class HueTemperature(GenericHueGaugeSensorEntity):
|
||||
return self.sensor.temperature / 100
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueBattery(GenericHueSensor, SensorEntity):
|
||||
"""Battery class for when a batt-powered device is only represented as an event."""
|
||||
|
||||
|
@ -82,6 +82,7 @@ async def async_setup_entry(
|
||||
register_items(api.sensors.tamper, HueTamperSensor)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueMotionSensor(HueBaseEntity, BinarySensorEntity):
|
||||
"""Representation of a Hue Motion sensor."""
|
||||
|
||||
@ -103,6 +104,7 @@ class HueMotionSensor(HueBaseEntity, BinarySensorEntity):
|
||||
return self.resource.motion.value
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueEntertainmentActiveSensor(HueBaseEntity, BinarySensorEntity):
|
||||
"""Representation of a Hue Entertainment Configuration as binary sensor."""
|
||||
|
||||
@ -126,6 +128,7 @@ class HueEntertainmentActiveSensor(HueBaseEntity, BinarySensorEntity):
|
||||
return self.resource.metadata.name
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueContactSensor(HueBaseEntity, BinarySensorEntity):
|
||||
"""Representation of a Hue Contact sensor."""
|
||||
|
||||
@ -147,6 +150,7 @@ class HueContactSensor(HueBaseEntity, BinarySensorEntity):
|
||||
return self.resource.contact_report.state != ContactState.CONTACT
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueTamperSensor(HueBaseEntity, BinarySensorEntity):
|
||||
"""Representation of a Hue Tamper sensor."""
|
||||
|
||||
|
@ -76,6 +76,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class GroupedHueLight(HueBaseEntity, LightEntity):
|
||||
"""Representation of a Grouped Hue light."""
|
||||
|
||||
|
@ -68,6 +68,7 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueLight(HueBaseEntity, LightEntity):
|
||||
"""Representation of a Hue light."""
|
||||
|
||||
|
@ -79,6 +79,7 @@ async def async_setup_entry(
|
||||
register_items(ctrl_base.zigbee_connectivity, HueZigbeeConnectivitySensor)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueSensorBase(HueBaseEntity, SensorEntity):
|
||||
"""Representation of a Hue sensor."""
|
||||
|
||||
@ -94,6 +95,7 @@ class HueSensorBase(HueBaseEntity, SensorEntity):
|
||||
self.controller = controller
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueTemperatureSensor(HueSensorBase):
|
||||
"""Representation of a Hue Temperature sensor."""
|
||||
|
||||
@ -111,6 +113,7 @@ class HueTemperatureSensor(HueSensorBase):
|
||||
return round(self.resource.temperature.value, 1)
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueLightLevelSensor(HueSensorBase):
|
||||
"""Representation of a Hue LightLevel (illuminance) sensor."""
|
||||
|
||||
@ -139,6 +142,7 @@ class HueLightLevelSensor(HueSensorBase):
|
||||
}
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueBatterySensor(HueSensorBase):
|
||||
"""Representation of a Hue Battery sensor."""
|
||||
|
||||
@ -164,6 +168,7 @@ class HueBatterySensor(HueSensorBase):
|
||||
return {"battery_state": self.resource.power_state.battery_state.value}
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class HueZigbeeConnectivitySensor(HueSensorBase):
|
||||
"""Representation of a Hue ZigbeeConnectivity sensor."""
|
||||
|
||||
|
@ -128,6 +128,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class InputButton(collection.CollectionEntity, ButtonEntity, RestoreEntity):
|
||||
"""Representation of a button."""
|
||||
|
||||
|
@ -246,6 +246,7 @@ class InputSelectStorageCollection(collection.DictStorageCollection):
|
||||
return {CONF_ID: item[CONF_ID]} | update_data
|
||||
|
||||
|
||||
# pylint: disable-next=hass-enforce-class-module
|
||||
class InputSelect(collection.CollectionEntity, SelectEntity, RestoreEntity):
|
||||
"""Representation of a select input."""
|
||||
|
||||
|
@ -3,49 +3,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from ast import ClassDef
|
||||
from dataclasses import dataclass
|
||||
|
||||
from astroid import nodes
|
||||
from pylint.checkers import BaseChecker
|
||||
from pylint.lint import PyLinter
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClassModuleMatch:
|
||||
"""Class for pattern matching."""
|
||||
|
||||
expected_module: str
|
||||
base_class: str
|
||||
|
||||
|
||||
_MODULES = [
|
||||
ClassModuleMatch("alarm_control_panel", "AlarmControlPanelEntityDescription"),
|
||||
ClassModuleMatch("assist_satellite", "AssistSatelliteEntityDescription"),
|
||||
ClassModuleMatch("binary_sensor", "BinarySensorEntityDescription"),
|
||||
ClassModuleMatch("button", "ButtonEntityDescription"),
|
||||
ClassModuleMatch("camera", "CameraEntityDescription"),
|
||||
ClassModuleMatch("climate", "ClimateEntityDescription"),
|
||||
ClassModuleMatch("coordinator", "DataUpdateCoordinator"),
|
||||
ClassModuleMatch("cover", "CoverEntityDescription"),
|
||||
ClassModuleMatch("date", "DateEntityDescription"),
|
||||
ClassModuleMatch("datetime", "DateTimeEntityDescription"),
|
||||
ClassModuleMatch("event", "EventEntityDescription"),
|
||||
ClassModuleMatch("image", "ImageEntityDescription"),
|
||||
ClassModuleMatch("image_processing", "ImageProcessingEntityDescription"),
|
||||
ClassModuleMatch("lawn_mower", "LawnMowerEntityDescription"),
|
||||
ClassModuleMatch("lock", "LockEntityDescription"),
|
||||
ClassModuleMatch("media_player", "MediaPlayerEntityDescription"),
|
||||
ClassModuleMatch("notify", "NotifyEntityDescription"),
|
||||
ClassModuleMatch("number", "NumberEntityDescription"),
|
||||
ClassModuleMatch("select", "SelectEntityDescription"),
|
||||
ClassModuleMatch("sensor", "SensorEntityDescription"),
|
||||
ClassModuleMatch("text", "TextEntityDescription"),
|
||||
ClassModuleMatch("time", "TimeEntityDescription"),
|
||||
ClassModuleMatch("update", "UpdateEntityDescription"),
|
||||
ClassModuleMatch("vacuum", "VacuumEntityDescription"),
|
||||
ClassModuleMatch("water_heater", "WaterHeaterEntityDescription"),
|
||||
ClassModuleMatch("weather", "WeatherEntityDescription"),
|
||||
]
|
||||
_MODULES: dict[str, set[str]] = {
|
||||
"air_quality": {"AirQualityEntity"},
|
||||
"alarm_control_panel": {
|
||||
"AlarmControlPanelEntity",
|
||||
"AlarmControlPanelEntityDescription",
|
||||
},
|
||||
"assist_satellite": {"AssistSatelliteEntity", "AssistSatelliteEntityDescription"},
|
||||
"binary_sensor": {"BinarySensorEntity", "BinarySensorEntityDescription"},
|
||||
"button": {"ButtonEntity", "ButtonEntityDescription"},
|
||||
"calendar": {"CalendarEntity"},
|
||||
"camera": {"CameraEntity", "CameraEntityDescription"},
|
||||
"climate": {"ClimateEntity", "ClimateEntityDescription"},
|
||||
"coordinator": {"DataUpdateCoordinator"},
|
||||
"conversation": {"ConversationEntity"},
|
||||
"cover": {"CoverEntity", "CoverEntityDescription"},
|
||||
"date": {"DateEntity", "DateEntityDescription"},
|
||||
"datetime": {"DateTimeEntity", "DateTimeEntityDescription"},
|
||||
"device_tracker": {"DeviceTrackerEntity"},
|
||||
"event": {"EventEntity", "EventEntityDescription"},
|
||||
"fan": {"FanEntity", "FanEntityDescription"},
|
||||
"geo_location": {"GeolocationEvent"},
|
||||
"humidifier": {"HumidifierEntity", "HumidifierEntityDescription"},
|
||||
"image": {"ImageEntity", "ImageEntityDescription"},
|
||||
"image_processing": {
|
||||
"ImageProcessingEntity",
|
||||
"ImageProcessingFaceEntity",
|
||||
"ImageProcessingEntityDescription",
|
||||
},
|
||||
"lawn_mower": {"LawnMowerEntity", "LawnMowerEntityDescription"},
|
||||
"light": {"LightEntity", "LightEntityDescription"},
|
||||
"lock": {"LockEntity", "LockEntityDescription"},
|
||||
"media_player": {"MediaPlayerEntity", "MediaPlayerEntityDescription"},
|
||||
"notify": {"NotifyEntity", "NotifyEntityDescription"},
|
||||
"number": {"NumberEntity", "NumberEntityDescription", "RestoreNumber"},
|
||||
"remote": {"RemoteEntity", "RemoteEntityDescription"},
|
||||
"select": {"SelectEntity", "SelectEntityDescription"},
|
||||
"sensor": {"RestoreSensor", "SensorEntity", "SensorEntityDescription"},
|
||||
"siren": {"SirenEntity", "SirenEntityDescription"},
|
||||
"stt": {"SpeechToTextEntity"},
|
||||
"switch": {"SwitchEntity", "SwitchEntityDescription"},
|
||||
"text": {"TextEntity", "TextEntityDescription"},
|
||||
"time": {"TimeEntity", "TimeEntityDescription"},
|
||||
"todo": {"TodoListEntity"},
|
||||
"tts": {"TextToSpeechEntity"},
|
||||
"update": {"UpdateEntityDescription"},
|
||||
"vacuum": {"VacuumEntity", "VacuumEntityDescription"},
|
||||
"wake_word": {"WakeWordDetectionEntity"},
|
||||
"water_heater": {"WaterHeaterEntity"},
|
||||
"weather": {
|
||||
"CoordinatorWeatherEntity",
|
||||
"SingleCoordinatorWeatherEntity",
|
||||
"WeatherEntity",
|
||||
"WeatherEntityDescription",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class HassEnforceClassModule(BaseChecker):
|
||||
@ -69,24 +86,24 @@ class HassEnforceClassModule(BaseChecker):
|
||||
if not root_name.startswith("homeassistant.components."):
|
||||
return
|
||||
parts = root_name.split(".")
|
||||
current_integration = parts[2]
|
||||
current_module = parts[3] if len(parts) > 3 else ""
|
||||
|
||||
ancestors: list[ClassDef] | None = None
|
||||
|
||||
for match in _MODULES:
|
||||
# Allow module.py and module/sub_module.py
|
||||
if current_module == match.expected_module:
|
||||
for expected_module, classes in _MODULES.items():
|
||||
if expected_module in (current_module, current_integration):
|
||||
continue
|
||||
|
||||
if ancestors is None:
|
||||
ancestors = list(node.ancestors()) # cache result for other modules
|
||||
|
||||
for ancestor in ancestors:
|
||||
if ancestor.name == match.base_class:
|
||||
if ancestor.name in classes:
|
||||
self.add_message(
|
||||
"hass-enforce-class-module",
|
||||
node=node,
|
||||
args=(match.base_class, match.expected_module),
|
||||
args=(ancestor.name, expected_module),
|
||||
)
|
||||
return
|
||||
|
||||
|
@ -63,6 +63,36 @@ def test_enforce_class_module_good(
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
"homeassistant.components.sensor",
|
||||
"homeassistant.components.sensor.entity",
|
||||
"homeassistant.components.pylint_test.sensor",
|
||||
"homeassistant.components.pylint_test.sensor.entity",
|
||||
],
|
||||
)
|
||||
def test_enforce_class_platform_good(
|
||||
linter: UnittestLinter,
|
||||
enforce_class_module_checker: BaseChecker,
|
||||
path: str,
|
||||
) -> None:
|
||||
"""Good test cases."""
|
||||
code = """
|
||||
class SensorEntity:
|
||||
pass
|
||||
|
||||
class CustomSensorEntity(SensorEntity):
|
||||
pass
|
||||
"""
|
||||
root_node = astroid.parse(code, path)
|
||||
walker = ASTWalker(linter)
|
||||
walker.add_checker(enforce_class_module_checker)
|
||||
|
||||
with assert_no_messages(linter):
|
||||
walker.walk(root_node)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
[
|
||||
|
Loading…
x
Reference in New Issue
Block a user