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:
parent
5fcdcbf9b9
commit
6bc2d11c5e
16 changed files with 102 additions and 15 deletions
|
@ -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):
|
||||
|
|
|
@ -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."""
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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__(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue