From 7b927b277252035a76d4dc9aec580bbf8132ceb4 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:51:54 +0000 Subject: [PATCH 1/3] Remove invalid shelly runtime_data checks --- .../components/shelly/coordinator.py | 6 +- .../components/shelly/test_device_trigger.py | 90 ------------------- 2 files changed, 2 insertions(+), 94 deletions(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 2a832c4dba4b93..0fe875589f137d 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -897,14 +897,13 @@ def get_block_coordinator_by_device_id( ) -> ShellyBlockCoordinator | None: """Get a Shelly block device coordinator for the given device id.""" dev_reg = dr.async_get(hass) + entry: ShellyConfigEntry | None if device := dev_reg.async_get(device_id): for config_entry in device.config_entries: entry = hass.config_entries.async_get_entry(config_entry) if ( entry and entry.state is ConfigEntryState.LOADED - and hasattr(entry, "runtime_data") - and isinstance(entry.runtime_data, ShellyEntryData) and (coordinator := entry.runtime_data.block) ): return coordinator @@ -917,14 +916,13 @@ def get_rpc_coordinator_by_device_id( ) -> ShellyRpcCoordinator | None: """Get a Shelly RPC device coordinator for the given device id.""" dev_reg = dr.async_get(hass) + entry: ShellyConfigEntry | None if device := dev_reg.async_get(device_id): for config_entry in device.config_entries: entry = hass.config_entries.async_get_entry(config_entry) if ( entry and entry.state is ConfigEntryState.LOADED - and hasattr(entry, "runtime_data") - and isinstance(entry.runtime_data, ShellyEntryData) and (coordinator := entry.runtime_data.rpc) ): return coordinator diff --git a/tests/components/shelly/test_device_trigger.py b/tests/components/shelly/test_device_trigger.py index b23f56ef4a9796..5becbe8fefc831 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -391,93 +391,3 @@ async def test_validate_trigger_invalid_triggers( "Invalid device automation trigger (type, subtype): ('single', 'button3')" in caplog.text ) - - -async def test_rpc_no_runtime_data( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - service_calls: list[ServiceCall], - mock_rpc_device: Mock, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Test the device trigger for the RPC device when there is no runtime_data in the entry.""" - entry = await init_integration(hass, 2) - monkeypatch.delattr(entry, "runtime_data") - device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0] - - assert await async_setup_component( - hass, - automation.DOMAIN, - { - automation.DOMAIN: [ - { - "trigger": { - CONF_PLATFORM: "device", - CONF_DOMAIN: DOMAIN, - CONF_DEVICE_ID: device.id, - CONF_TYPE: "single_push", - CONF_SUBTYPE: "button1", - }, - "action": { - "service": "test.automation", - "data_template": {"some": "test_trigger_single_push"}, - }, - }, - ] - }, - ) - message = { - CONF_DEVICE_ID: device.id, - ATTR_CLICK_TYPE: "single_push", - ATTR_CHANNEL: 1, - } - hass.bus.async_fire(EVENT_SHELLY_CLICK, message) - await hass.async_block_till_done() - - assert len(service_calls) == 1 - assert service_calls[0].data["some"] == "test_trigger_single_push" - - -async def test_block_no_runtime_data( - hass: HomeAssistant, - device_registry: dr.DeviceRegistry, - service_calls: list[ServiceCall], - mock_block_device: Mock, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Test the device trigger for the block device when there is no runtime_data in the entry.""" - entry = await init_integration(hass, 1) - monkeypatch.delattr(entry, "runtime_data") - device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0] - - assert await async_setup_component( - hass, - automation.DOMAIN, - { - automation.DOMAIN: [ - { - "trigger": { - CONF_PLATFORM: "device", - CONF_DOMAIN: DOMAIN, - CONF_DEVICE_ID: device.id, - CONF_TYPE: "single", - CONF_SUBTYPE: "button1", - }, - "action": { - "service": "test.automation", - "data_template": {"some": "test_trigger_single"}, - }, - }, - ] - }, - ) - message = { - CONF_DEVICE_ID: device.id, - ATTR_CLICK_TYPE: "single", - ATTR_CHANNEL: 1, - } - hass.bus.async_fire(EVENT_SHELLY_CLICK, message) - await hass.async_block_till_done() - - assert len(service_calls) == 1 - assert service_calls[0].data["some"] == "test_trigger_single" From bc35908e5f60c94ade12257f31a5263208ec8751 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:59:04 +0000 Subject: [PATCH 2/3] Add domain check Co-authored-by: Copilot --- homeassistant/components/shelly/coordinator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 0fe875589f137d..619a9fced93360 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -904,6 +904,7 @@ def get_block_coordinator_by_device_id( if ( entry and entry.state is ConfigEntryState.LOADED + and entry.domain == DOMAIN and (coordinator := entry.runtime_data.block) ): return coordinator @@ -923,6 +924,7 @@ def get_rpc_coordinator_by_device_id( if ( entry and entry.state is ConfigEntryState.LOADED + and entry.domain == DOMAIN and (coordinator := entry.runtime_data.rpc) ): return coordinator From 616c28c7ed3becdb3af617fa05bca2a8d6eb653a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:56:42 +0000 Subject: [PATCH 3/3] Adjust --- .../components/shelly/coordinator.py | 8 +- .../components/shelly/test_device_trigger.py | 100 ++++++++++++++++++ 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/shelly/coordinator.py b/homeassistant/components/shelly/coordinator.py index 619a9fced93360..2a832c4dba4b93 100644 --- a/homeassistant/components/shelly/coordinator.py +++ b/homeassistant/components/shelly/coordinator.py @@ -897,14 +897,14 @@ def get_block_coordinator_by_device_id( ) -> ShellyBlockCoordinator | None: """Get a Shelly block device coordinator for the given device id.""" dev_reg = dr.async_get(hass) - entry: ShellyConfigEntry | None if device := dev_reg.async_get(device_id): for config_entry in device.config_entries: entry = hass.config_entries.async_get_entry(config_entry) if ( entry and entry.state is ConfigEntryState.LOADED - and entry.domain == DOMAIN + and hasattr(entry, "runtime_data") + and isinstance(entry.runtime_data, ShellyEntryData) and (coordinator := entry.runtime_data.block) ): return coordinator @@ -917,14 +917,14 @@ def get_rpc_coordinator_by_device_id( ) -> ShellyRpcCoordinator | None: """Get a Shelly RPC device coordinator for the given device id.""" dev_reg = dr.async_get(hass) - entry: ShellyConfigEntry | None if device := dev_reg.async_get(device_id): for config_entry in device.config_entries: entry = hass.config_entries.async_get_entry(config_entry) if ( entry and entry.state is ConfigEntryState.LOADED - and entry.domain == DOMAIN + and hasattr(entry, "runtime_data") + and isinstance(entry.runtime_data, ShellyEntryData) and (coordinator := entry.runtime_data.rpc) ): return coordinator diff --git a/tests/components/shelly/test_device_trigger.py b/tests/components/shelly/test_device_trigger.py index 5becbe8fefc831..b88418d71f1a79 100644 --- a/tests/components/shelly/test_device_trigger.py +++ b/tests/components/shelly/test_device_trigger.py @@ -391,3 +391,103 @@ async def test_validate_trigger_invalid_triggers( "Invalid device automation trigger (type, subtype): ('single', 'button3')" in caplog.text ) + + +async def test_rpc_no_runtime_data( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + service_calls: list[ServiceCall], + mock_rpc_device: Mock, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test the device trigger for the RPC device when there is no runtime_data in the entry.""" + entry = await init_integration(hass, 2) + # Cache initial runtime_data + runtime_data = entry.runtime_data + monkeypatch.delattr(entry, "runtime_data") + device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0] + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: "single_push", + CONF_SUBTYPE: "button1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_single_push"}, + }, + }, + ] + }, + ) + message = { + CONF_DEVICE_ID: device.id, + ATTR_CLICK_TYPE: "single_push", + ATTR_CHANNEL: 1, + } + hass.bus.async_fire(EVENT_SHELLY_CLICK, message) + await hass.async_block_till_done() + + assert len(service_calls) == 1 + assert service_calls[0].data["some"] == "test_trigger_single_push" + + # Restore runtime_data to avoid issues on cleanup + entry.runtime_data = runtime_data + + +async def test_block_no_runtime_data( + hass: HomeAssistant, + device_registry: dr.DeviceRegistry, + service_calls: list[ServiceCall], + mock_block_device: Mock, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Test the device trigger for the block device when there is no runtime_data in the entry.""" + entry = await init_integration(hass, 1) + # Cache initial runtime_data + runtime_data = entry.runtime_data + monkeypatch.delattr(entry, "runtime_data") + device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0] + + assert await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + CONF_PLATFORM: "device", + CONF_DOMAIN: DOMAIN, + CONF_DEVICE_ID: device.id, + CONF_TYPE: "single", + CONF_SUBTYPE: "button1", + }, + "action": { + "service": "test.automation", + "data_template": {"some": "test_trigger_single"}, + }, + }, + ] + }, + ) + message = { + CONF_DEVICE_ID: device.id, + ATTR_CLICK_TYPE: "single", + ATTR_CHANNEL: 1, + } + hass.bus.async_fire(EVENT_SHELLY_CLICK, message) + await hass.async_block_till_done() + + assert len(service_calls) == 1 + assert service_calls[0].data["some"] == "test_trigger_single" + + # Restore runtime_data to avoid issues on cleanup + entry.runtime_data = runtime_data