Add base Entity class to enforce-class-module pylint plugin (#126026)

* Add base Entity class to enforcé-class-module pylint plugin

* Ignore bluetooth

* Ignore hue

* Ignore dominos

* Ignore ffmpeg

* Ignore mqtt

* Ignore microsoft_face

* Ignore plant

* Ignore point

* Ignore rfxtrx

* Ignore template

* Ignore tag

* Ignore deconz
This commit is contained in:
epenet 2024-09-18 20:38:45 +02:00 committed by GitHub
parent 5fcdcbf9b9
commit 6bc2d11c5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 102 additions and 15 deletions

View file

@ -597,6 +597,7 @@ class PassiveBluetoothDataProcessor[_T, _DataT]:
self.async_update_listeners(new_data, was_available, changed_entity_keys)
# pylint: disable-next=hass-enforce-class-module
class PassiveBluetoothProcessorEntity[
_PassiveBluetoothDataProcessorT: PassiveBluetoothDataProcessor[Any, Any]
](Entity):

View file

@ -68,6 +68,7 @@ class DeconzBase[_DeviceT: _DeviceType]:
)
# pylint: disable-next=hass-enforce-class-module
class DeconzDevice[_DeviceT: _DeviceType](DeconzBase[_DeviceT], Entity):
"""Representation of a deCONZ device."""

View file

@ -182,7 +182,7 @@ class DominosProductListView(http.HomeAssistantView):
return self.json(self.dominos.get_menu())
class DominosOrder(Entity):
class DominosOrder(Entity): # pylint: disable=hass-enforce-class-module
"""Represents a Dominos order entity."""
def __init__(self, order_info, dominos):

View file

@ -176,7 +176,7 @@ class FFmpegManager:
return CONTENT_TYPE_MULTIPART.format("ffserver")
class FFmpegBase[_HAFFmpegT: HAFFmpeg](Entity):
class FFmpegBase[_HAFFmpegT: HAFFmpeg](Entity): # pylint: disable=hass-enforce-class-module
"""Interface object for FFmpeg."""
_attr_should_poll = False

View file

@ -165,7 +165,7 @@ class SensorManager:
self._component_add_entities[platform](value)
class GenericHueSensor(GenericHueDevice, entity.Entity):
class GenericHueSensor(GenericHueDevice, entity.Entity): # pylint: disable=hass-enforce-class-module
"""Representation of a Hue sensor."""
should_poll = False

View file

@ -10,7 +10,7 @@ from ..const import (
)
class GenericHueDevice(entity.Entity):
class GenericHueDevice(entity.Entity): # pylint: disable=hass-enforce-class-module
"""Representation of a Hue device."""
def __init__(self, sensor, name, bridge, primary_sensor=None):

View file

@ -34,7 +34,7 @@ RESOURCE_TYPE_NAMES = {
}
class HueBaseEntity(Entity):
class HueBaseEntity(Entity): # pylint: disable=hass-enforce-class-module
"""Generic Entity Class for a Hue resource."""
_attr_should_poll = False

View file

@ -214,7 +214,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
class MicrosoftFaceGroupEntity(Entity):
class MicrosoftFaceGroupEntity(Entity): # pylint: disable=hass-enforce-class-module
"""Person-Group state/data Entity."""
_attr_should_poll = False

View file

@ -369,7 +369,7 @@ def init_entity_id_from_config(
)
class MqttAttributesMixin(Entity):
class MqttAttributesMixin(Entity): # pylint: disable=hass-enforce-class-module
"""Mixin used for platforms that support JSON attributes."""
_attributes_extra_blocked: frozenset[str] = frozenset()
@ -454,7 +454,7 @@ class MqttAttributesMixin(Entity):
_LOGGER.warning("JSON result was not a dictionary")
class MqttAvailabilityMixin(Entity):
class MqttAvailabilityMixin(Entity): # pylint: disable=hass-enforce-class-module
"""Mixin used for platforms that report availability."""
def __init__(self, config: ConfigType) -> None:
@ -799,7 +799,7 @@ class MqttDiscoveryDeviceUpdateMixin(ABC):
"""Handle the cleanup of platform specific parts, extend to the platform."""
class MqttDiscoveryUpdateMixin(Entity):
class MqttDiscoveryUpdateMixin(Entity): # pylint: disable=hass-enforce-class-module
"""Mixin used to handle updated discovery message for entity based platforms."""
def __init__(
@ -1021,7 +1021,7 @@ def device_info_from_specifications(
return info
class MqttEntityDeviceInfo(Entity):
class MqttEntityDeviceInfo(Entity): # pylint: disable=hass-enforce-class-module
"""Mixin used for mqtt platforms that support the device registry."""
def __init__(

View file

@ -127,7 +127,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
class Plant(Entity):
class Plant(Entity): # pylint: disable=hass-enforce-class-module
"""Plant monitors the well-being of a plant.
It also checks the measurements against

View file

@ -257,7 +257,7 @@ class MinutPointClient:
return await self._client.alarm_arm(home_id)
class MinutPointEntity(Entity):
class MinutPointEntity(Entity): # pylint: disable=hass-enforce-class-module # see PR 118243
"""Base Entity used by the sensors."""
_attr_should_poll = False

View file

@ -93,7 +93,7 @@ async def async_setup_entry(
)
class RfxtrxOffDelayMixin(Entity):
class RfxtrxOffDelayMixin(Entity): # pylint: disable=hass-enforce-class-module
"""Mixin to support timeouts on data.
Many 433 devices only send data when active. They will

View file

@ -360,7 +360,7 @@ async def async_scan_tag(
_LOGGER.debug("Tag: %s scanned by device: %s", tag_id, device_id)
class TagEntity(Entity):
class TagEntity(Entity): # pylint: disable=hass-enforce-class-module
"""Representation of a Tag entity."""
_unrecorded_attributes = frozenset({TAG_ID})

View file

@ -244,7 +244,7 @@ class _TemplateAttribute:
return
class TemplateEntity(Entity):
class TemplateEntity(Entity): # pylint: disable=hass-enforce-class-module
"""Entity that uses templates to calculate attributes."""
_attr_available = True

View file

@ -8,6 +8,8 @@ from astroid import nodes
from pylint.checkers import BaseChecker
from pylint.lint import PyLinter
from homeassistant.const import Platform
_MODULES: dict[str, set[str]] = {
"air_quality": {"AirQualityEntity"},
"alarm_control_panel": {
@ -63,6 +65,7 @@ _MODULES: dict[str, set[str]] = {
"WeatherEntityDescription",
},
}
_PLATFORMS: set[str] = {platform.value for platform in Platform}
class HassEnforceClassModule(BaseChecker):
@ -89,6 +92,18 @@ class HassEnforceClassModule(BaseChecker):
current_integration = parts[2]
current_module = parts[3] if len(parts) > 3 else ""
if current_module != "entity" and current_integration not in _PLATFORMS:
top_level_ancestors = list(node.ancestors(recurs=False))
for ancestor in top_level_ancestors:
if ancestor.name == "Entity":
self.add_message(
"hass-enforce-class-module",
node=node,
args=(ancestor.name, "entity"),
)
return
ancestors: list[ClassDef] | None = None
for expected_module, classes in _MODULES.items():

View file

@ -192,3 +192,73 @@ def test_enforce_class_module_bad_nested(
),
):
walker.walk(root_node)
@pytest.mark.parametrize(
"path",
[
"homeassistant.components.sensor",
"homeassistant.components.sensor.entity",
"homeassistant.components.pylint_test.entity",
],
)
def test_enforce_entity_good(
linter: UnittestLinter,
enforce_class_module_checker: BaseChecker,
path: str,
) -> None:
"""Good test cases."""
code = """
class Entity:
pass
class CustomEntity(Entity):
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",
[
"homeassistant.components.pylint_test",
"homeassistant.components.pylint_test.select",
"homeassistant.components.pylint_test.select.entity",
],
)
def test_enforce_entity_bad(
linter: UnittestLinter,
enforce_class_module_checker: BaseChecker,
path: str,
) -> None:
"""Good test cases."""
code = """
class Entity:
pass
class CustomEntity(Entity):
pass
"""
root_node = astroid.parse(code, path)
walker = ASTWalker(linter)
walker.add_checker(enforce_class_module_checker)
with assert_adds_messages(
linter,
MessageTest(
msg_id="hass-enforce-class-module",
line=5,
node=root_node.body[1],
args=("Entity", "entity"),
confidence=UNDEFINED,
col_offset=0,
end_line=5,
end_col_offset=18,
),
):
walker.walk(root_node)