def test_number_entity(linter: UnittestLinter, type_hint_checker: BaseChecker) -> None: """Ensure valid hints are accepted for number entity.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False # Ensure that device class is valid despite Entity inheritance # Ensure that `int` is valid for `float` return type class_node = astroid.extract_node( """ class Entity(): pass class RestoreEntity(Entity): pass class NumberEntity(Entity): pass class MyNumber( #@ RestoreEntity, NumberEntity ): @property def device_class(self) -> NumberDeviceClass: pass @property def native_value(self) -> int: pass """, "homeassistant.components.pylint_test.number", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_valid_config_flow_step( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Ensure valid hints are accepted for ConfigFlow step.""" class_node = astroid.extract_node( """ class FlowHandler(): pass class ConfigFlow(FlowHandler): pass class AxisFlowHandler( #@ ConfigFlow, domain=AXIS_DOMAIN ): async def async_step_zeroconf( self, device_config: ZeroconfServiceInfo ) -> FlowResult: pass """, "homeassistant.components.pylint_test.config_flow", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_media_player_entity( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Ensure valid hints are accepted for media_player entity.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False class_node = astroid.extract_node( """ class Entity(): pass class MediaPlayerEntity(Entity): pass class MyMediaPlayer( #@ MediaPlayerEntity ): async def async_get_media_image(self) -> tuple[bytes | None, str | None]: pass """, "homeassistant.components.pylint_test.media_player", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_valid_mapping_return_type( linter: UnittestLinter, type_hint_checker: BaseChecker, return_hint: str, ) -> None: """Check that Mapping[xxx, Any] accepts both Mapping and dict.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False class_node = astroid.extract_node( f""" class Entity(): pass class ToggleEntity(Entity): pass class FanEntity(ToggleEntity): pass class MyFanA( #@ FanEntity ): @property def capability_attributes( self ){return_hint}: pass """, "homeassistant.components.pylint_test.fan", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_invalid_long_tuple( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Check invalid entity properties are ignored by default.""" # Set ignore option type_hint_checker.config.ignore_missing_annotations = False class_node, rgbw_node, rgbww_node = astroid.extract_node( """ class Entity(): pass class ToggleEntity(Entity): pass class LightEntity(ToggleEntity): pass class TestLight( #@ LightEntity ): @property def rgbw_color( #@ self ) -> tuple[int, int, int, int, int]: pass @property def rgbww_color( #@ self ) -> tuple[int, int, int, int, float]: pass """, "homeassistant.components.pylint_test.light", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-return-type", node=rgbw_node, args=(["tuple[int, int, int, int]", None], "rgbw_color"), line=15, col_offset=4, end_line=15, end_col_offset=18, ), pylint.testutils.MessageTest( msg_id="hass-return-type", node=rgbww_node, args=(["tuple[int, int, int, int, int]", None], "rgbww_color"), line=21, col_offset=4, end_line=21, end_col_offset=19, ), ): type_hint_checker.visit_classdef(class_node)
def test_invalid_config_flow_async_get_options_flow( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Ensure invalid hints are rejected for ConfigFlow async_get_options_flow.""" # AxisOptionsFlow doesn't inherit OptionsFlow, and therefore should fail class_node, func_node, arg_node = astroid.extract_node( """ class FlowHandler(): pass class ConfigFlow(FlowHandler): pass class OptionsFlow(FlowHandler): pass class AxisOptionsFlow(): pass class AxisFlowHandler( #@ ConfigFlow, domain=AXIS_DOMAIN ): def async_get_options_flow( #@ config_entry #@ ) -> AxisOptionsFlow: return AxisOptionsFlow(config_entry) """, "homeassistant.components.pylint_test.config_flow", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-argument-type", node=arg_node, args=(1, "ConfigEntry", "async_get_options_flow"), line=18, col_offset=8, end_line=18, end_col_offset=20, ), pylint.testutils.MessageTest( msg_id="hass-return-type", node=func_node, args=("OptionsFlow", "async_get_options_flow"), line=17, col_offset=4, end_line=17, end_col_offset=30, ), ): type_hint_checker.visit_classdef(class_node)
def test_invalid_config_flow_step( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Ensure invalid hints are rejected for ConfigFlow step.""" class_node, func_node, arg_node = astroid.extract_node( """ class FlowHandler(): pass class ConfigFlow(FlowHandler): pass class AxisFlowHandler( #@ ConfigFlow, domain=AXIS_DOMAIN ): async def async_step_zeroconf( #@ self, device_config: dict #@ ): pass """, "homeassistant.components.pylint_test.config_flow", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-argument-type", node=arg_node, args=(2, "ZeroconfServiceInfo", "async_step_zeroconf"), line=13, col_offset=8, end_line=13, end_col_offset=27, ), pylint.testutils.MessageTest( msg_id="hass-return-type", node=func_node, args=("FlowResult", "async_step_zeroconf"), line=11, col_offset=4, end_line=11, end_col_offset=33, ), ): type_hint_checker.visit_classdef(class_node)
def test_invalid_mapping_return_type( linter: UnittestLinter, type_hint_checker: BaseChecker, return_hint: str, ) -> None: """Check that Mapping[xxx, Any] doesn't accept invalid Mapping or dict.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False class_node, property_node = astroid.extract_node( f""" class Entity(): pass class ToggleEntity(Entity): pass class FanEntity(ToggleEntity): pass class MyFanA( #@ FanEntity ): @property def capability_attributes( #@ self ){return_hint}: pass """, "homeassistant.components.pylint_test.fan", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-return-type", node=property_node, args=(["Mapping[str, Any]", None], "capability_attributes"), line=15, col_offset=4, end_line=15, end_col_offset=29, ), ): type_hint_checker.visit_classdef(class_node)
def test_invalid_device_class( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Ensure invalid hints are rejected for entity device_class.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False class_node, prop_node = astroid.extract_node( """ class Entity(): pass class CoverEntity(Entity): pass class MyCover( #@ CoverEntity ): @property def device_class( #@ self ): pass """, "homeassistant.components.pylint_test.cover", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-return-type", node=prop_node, args=(["CoverDeviceClass", "str", None], "device_class"), line=12, col_offset=4, end_line=12, end_col_offset=20, ), ): type_hint_checker.visit_classdef(class_node)
def test_valid_long_tuple( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Check invalid entity properties are ignored by default.""" # Set ignore option type_hint_checker.config.ignore_missing_annotations = False class_node, _, _ = astroid.extract_node( """ class Entity(): pass class ToggleEntity(Entity): pass class LightEntity(ToggleEntity): pass class TestLight( #@ LightEntity ): @property def rgbw_color( #@ self ) -> tuple[int, int, int, int]: pass @property def rgbww_color( #@ self ) -> tuple[int, int, int, int, int]: pass """, "homeassistant.components.pylint_test.light", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_valid_config_flow_async_get_options_flow( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Ensure valid hints are accepted for ConfigFlow async_get_options_flow.""" class_node = astroid.extract_node( """ class FlowHandler(): pass class ConfigFlow(FlowHandler): pass class OptionsFlow(FlowHandler): pass class AxisOptionsFlow(OptionsFlow): pass class OtherOptionsFlow(OptionsFlow): pass class AxisFlowHandler( #@ ConfigFlow, domain=AXIS_DOMAIN ): def async_get_options_flow( config_entry: ConfigEntry ) -> AxisOptionsFlow | OtherOptionsFlow | OptionsFlow: if self.use_other: return OtherOptionsFlow(config_entry) return AxisOptionsFlow(config_entry) """, "homeassistant.components.pylint_test.config_flow", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_vacuum_entity(linter: UnittestLinter, type_hint_checker: BaseChecker) -> None: """Ensure valid hints are accepted for vacuum entity.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False # Ensure that `dict | list | None` is valid for params class_node = astroid.extract_node( """ class Entity(): pass class ToggleEntity(Entity): pass class _BaseVacuum(Entity): pass class VacuumEntity(_BaseVacuum, ToggleEntity): pass class MyVacuum( #@ VacuumEntity ): def send_command( self, command: str, params: dict[str, Any] | list[Any] | None = None, **kwargs: Any, ) -> None: pass """, "homeassistant.components.pylint_test.vacuum", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_ignore_invalid_entity_properties( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Check invalid entity properties are ignored by default.""" # Set ignore option type_hint_checker.config.ignore_missing_annotations = True class_node = astroid.extract_node( """ class Entity(): pass class LockEntity(Entity): pass class DoorLock( #@ LockEntity ): @property def changed_by( self ): pass async def async_lock( self, **kwargs ) -> bool: pass """, "homeassistant.components.pylint_test.lock", ) type_hint_checker.visit_module(class_node.parent) with assert_no_messages(linter): type_hint_checker.visit_classdef(class_node)
def test_named_arguments( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Check missing entity properties when ignore_missing_annotations is False.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False class_node, func_node, percentage_node, preset_mode_node = astroid.extract_node( """ class Entity(): pass class ToggleEntity(Entity): pass class FanEntity(ToggleEntity): pass class MyFan( #@ FanEntity ): async def async_turn_on( #@ self, percentage, #@ *, preset_mode: str, #@ **kwargs ) -> bool: pass """, "homeassistant.components.pylint_test.fan", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-argument-type", node=percentage_node, args=("percentage", "int | None", "async_turn_on"), line=16, col_offset=8, end_line=16, end_col_offset=18, ), pylint.testutils.MessageTest( msg_id="hass-argument-type", node=preset_mode_node, args=("preset_mode", "str | None", "async_turn_on"), line=18, col_offset=8, end_line=18, end_col_offset=24, ), pylint.testutils.MessageTest( msg_id="hass-argument-type", node=func_node, args=("kwargs", "Any", "async_turn_on"), line=14, col_offset=4, end_line=14, end_col_offset=27, ), pylint.testutils.MessageTest( msg_id="hass-return-type", node=func_node, args=("None", "async_turn_on"), line=14, col_offset=4, end_line=14, end_col_offset=27, ), ): type_hint_checker.visit_classdef(class_node)
def test_invalid_entity_properties( linter: UnittestLinter, type_hint_checker: BaseChecker ) -> None: """Check missing entity properties when ignore_missing_annotations is False.""" # Set bypass option type_hint_checker.config.ignore_missing_annotations = False class_node, prop_node, func_node = astroid.extract_node( """ class Entity(): pass class LockEntity(Entity): pass class DoorLock( #@ LockEntity ): @property def changed_by( #@ self ): pass async def async_lock( #@ self, **kwargs ) -> bool: pass """, "homeassistant.components.pylint_test.lock", ) type_hint_checker.visit_module(class_node.parent) with assert_adds_messages( linter, pylint.testutils.MessageTest( msg_id="hass-return-type", node=prop_node, args=(["str", None], "changed_by"), line=12, col_offset=4, end_line=12, end_col_offset=18, ), pylint.testutils.MessageTest( msg_id="hass-argument-type", node=func_node, args=("kwargs", "Any", "async_lock"), line=17, col_offset=4, end_line=17, end_col_offset=24, ), pylint.testutils.MessageTest( msg_id="hass-return-type", node=func_node, args=("None", "async_lock"), line=17, col_offset=4, end_line=17, end_col_offset=24, ), ): type_hint_checker.visit_classdef(class_node)