Compare commits

..

14 Commits

Author SHA1 Message Date
Erik 7ef88f550f Adjust compound conditions 2026-04-23 17:17:36 +02:00
Erik c283826369 Update websocket_api.handle_test_condition 2026-04-23 17:15:39 +02:00
Erik 13565f5c94 Adjust EntityConditionBase 2026-04-23 17:15:39 +02:00
Erik c208d68292 Update compound conditions 2026-04-23 17:15:39 +02:00
Erik 8bdb5e7a3c Add duration support to cover conditions 2026-04-23 17:15:39 +02:00
Erik d8d8bb23a5 Add state tracking to EntityConditionBase 2026-04-23 17:15:39 +02:00
Erik 5a10e105a8 Migrate compound conditions to ConditionChecker 2026-04-23 17:15:39 +02:00
Erik 65a68c138c Reintroduce ConditionCheckParams 2026-04-23 17:14:48 +02:00
Erik 8237c4db12 Adjust 2026-04-23 16:52:25 +02:00
Erik d380ff61a5 Address review comments 2026-04-23 16:42:16 +02:00
Erik 0473407d38 Add ConditionChecker.async_on_unload 2026-04-23 16:26:03 +02:00
Erik f7aecb654b Log exceptions in cleanup 2026-04-23 13:48:00 +02:00
Erik f7a91721dc Adjust according to feedback 2026-04-23 07:50:05 +02:00
Erik d200e547e1 Refactor condition API 2026-04-22 13:28:44 +02:00
253 changed files with 1501 additions and 2216 deletions
+4 -5
View File
@@ -27,13 +27,12 @@ description: Reviews GitHub pull requests and provides feedback comments. This i
- No need to highlight things that are already good.
## Output format:
- List specific comments for each file/line that needs attention.
- List specific comments for each file/line that needs attention
- In the end, summarize with an overall assessment (approve, request changes, or comment) and bullet point list of changes suggested, if any.
- Example output:
```
Overall assessment: request changes.
- [CRITICAL] sensor.py:143 - Memory leak
- [PROBLEM] data_processing.py:87 - Inefficient algorithm
- [SUGGESTION] test_init.py:45 - Improve x variable name
- [CRITICAL] Memory leak in homeassistant/components/sensor/my_sensor.py:143
- [PROBLEM] Inefficient algorithm in homeassistant/helpers/data_processing.py:87
- [SUGGESTION] Improve variable naming in homeassistant/helpers/config_validation.py:45
```
- Make sure to include the file and line number when possible in the bullet points.
@@ -1,5 +1,5 @@
---
name: ha-integration-knowledge
name: Home Assistant Integration knowledge
description: Everything you need to know to build, test and review Home Assistant Integrations. If you're looking at an integration, you must use this as your primary reference.
---
@@ -14,8 +14,6 @@ description: Everything you need to know to build, test and review Home Assistan
- Do NOT allow users to set config entry names in config flows. Names are automatically generated or can be customized later in UI. Exception: helper integrations may allow custom names.
- For entity actions and entity services, avoid requesting redundant defensive checks for fields already enforced by Home Assistant validation schemas and entity filters; only request extra guards when values bypass validation or are transformed unsafely.
- When validation guarantees a key is present, prefer direct dictionary indexing (`data["key"]`) over `.get("key")` so invalid assumptions fail fast.
- Integrations should be thin wrappers. Protocol parsing, device state machines, or other domain logic belong in a separate PyPI library, not in the integration itself. If unsure, ask before inlining.
- "potato" is a forbidden word for an integration and should never be used.
The following platforms have extra guidelines:
- **Diagnostics**: [`platform-diagnostics.md`](platform-diagnostics.md) for diagnostic data collection
+1 -1
View File
@@ -38,4 +38,4 @@ When validation guarantees a dict key exists, prefer direct key access (`data["k
# Skills
- ha-integration-knowledge: .claude/skills/ha-integration-knowledge/SKILL.md
- Home Assistant Integration knowledge: .claude/skills/integrations/SKILL.md
+2 -2
View File
@@ -28,11 +28,11 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
category: "/language:python"
@@ -945,10 +945,7 @@ class PipelineRun:
try:
# Transcribe audio stream
stt_vad: VoiceCommandSegmenter | None = None
if (
self.audio_settings.is_vad_enabled
and self.stt_provider.audio_processing.requires_external_vad
):
if self.audio_settings.is_vad_enabled:
stt_vad = VoiceCommandSegmenter(
silence_seconds=self.audio_settings.silence_seconds
)
@@ -1,5 +1,4 @@
"""The Broadlink integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -34,8 +34,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink climate entities."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
if device.api.type in DOMAINS_AND_TYPES[Platform.CLIMATE]:
@@ -133,8 +133,6 @@ class BroadlinkDevice[_ApiT: blk.Device = blk.Device]:
await coordinator.async_config_entry_first_refresh()
self.update_manager = update_manager
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
self.hass.data[DOMAIN].devices[config.entry_id] = self
self.reset_jobs.append(config.add_update_listener(self.async_update))
@@ -32,8 +32,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink light."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
lights = []
@@ -95,8 +95,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Broadlink remote."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
remote = BroadlinkRemote(
device,
@@ -31,8 +31,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink select."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkDayOfWeek(device)])
@@ -108,8 +108,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink sensor."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
sensor_data = device.update_manager.coordinator.data
sensors = [
@@ -1,5 +1,4 @@
"""Support for Broadlink switches."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -22,8 +22,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink time."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkTime(device)])
@@ -1,5 +1,4 @@
"""Component to embed Google Cast."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
-2
View File
@@ -65,8 +65,6 @@ class ChromecastInfo:
"""
cast_info = self.cast_info
if self.cast_info.cast_type is None or self.cast_info.manufacturer is None:
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
unknown_models = hass.data[DOMAIN]["unknown_models"]
if self.cast_info.model_name not in unknown_models:
# Manufacturer and cast type is not available in mDNS data,
@@ -1,5 +1,4 @@
"""Provide functionality to interact with Cast devices on the network."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
+6 -1
View File
@@ -4,7 +4,11 @@ from collections.abc import Mapping
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.condition import Condition, EntityConditionBase
from homeassistant.helpers.condition import (
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR,
Condition,
EntityConditionBase,
)
from .const import ATTR_IS_CLOSED, DOMAIN, CoverDeviceClass
from .models import CoverDomainSpec
@@ -14,6 +18,7 @@ class CoverConditionBase(EntityConditionBase):
"""Base condition for cover state checks."""
_domain_specs: Mapping[str, CoverDomainSpec]
_schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR
def is_valid_state(self, entity_state: State) -> bool:
"""Check if the state matches the expected cover state."""
@@ -8,6 +8,11 @@
options:
- all
- any
for:
required: true
default: 00:00:00
selector:
duration:
awning_is_closed:
fields: *condition_common_fields
@@ -1,6 +1,7 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"condition_for_name": "For at least",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
@@ -10,6 +11,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Awning is closed"
@@ -19,6 +23,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Awning is open"
@@ -28,6 +35,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Blind is closed"
@@ -37,6 +47,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Blind is open"
@@ -46,6 +59,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Curtain is closed"
@@ -55,6 +71,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Curtain is open"
@@ -64,6 +83,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Shade is closed"
@@ -73,6 +95,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Shade is open"
@@ -82,6 +107,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Shutter is closed"
@@ -91,6 +119,9 @@
"fields": {
"behavior": {
"name": "[%key:component::cover::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::cover::common::condition_for_name%]"
}
},
"name": "Shutter is open"
@@ -7,11 +7,10 @@ from typing import Any, Protocol
import voluptuous as vol
from homeassistant.const import CONF_DOMAIN, CONF_OPTIONS
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.condition import (
Condition,
ConditionChecker,
ConditionCheckerType,
ConditionConfig,
)
@@ -54,6 +53,7 @@ class DeviceCondition(Condition):
"""Device condition."""
_config: ConfigType
_platform_checker: ConditionCheckerType
@classmethod
async def async_validate_complete_config(
@@ -87,20 +87,20 @@ class DeviceCondition(Condition):
assert config.options is not None
self._config = config.options
async def async_get_checker(self) -> ConditionChecker:
async def async_setup(self) -> None:
"""Test a device condition."""
platform = await async_get_device_automation_platform(
self._hass, self._config[CONF_DOMAIN], DeviceAutomationType.CONDITION
)
platform_checker = platform.async_condition_from_config(
self._platform_checker = platform.async_condition_from_config(
self._hass, self._config
)
def checker(variables: TemplateVarsType = None, **kwargs: Any) -> bool:
result = platform_checker(self._hass, variables)
return result is not False
return checker
@callback
def _async_check(self, variables: TemplateVarsType = None, **kwargs: Any) -> bool:
"""Check the condition."""
result = self._platform_checker(self._hass, variables)
return result is not False
CONDITIONS: dict[str, type[Condition]] = {
@@ -1,5 +1,4 @@
"""Data used by this integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
-1
View File
@@ -1,5 +1,4 @@
"""Wrapper for media_source around async_upnp_client's DmsDevice ."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -8,6 +8,11 @@
options:
- all
- any
for:
required: true
default: 00:00:00
selector:
duration:
is_closed:
fields: *condition_common_fields
@@ -1,6 +1,7 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"condition_for_name": "For at least",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
@@ -10,6 +11,9 @@
"fields": {
"behavior": {
"name": "[%key:component::door::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::door::common::condition_for_name%]"
}
},
"name": "Door is closed"
@@ -19,6 +23,9 @@
"fields": {
"behavior": {
"name": "[%key:component::door::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::door::common::condition_for_name%]"
}
},
"name": "Door is open"
@@ -13,7 +13,6 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import DOMAIN
@@ -36,27 +35,6 @@ class DucoConfigFlow(ConfigFlow, domain=DOMAIN):
_host: str
_box_name: str
async def async_step_dhcp(
self, discovery_info: DhcpServiceInfo
) -> ConfigFlowResult:
"""Handle DHCP discovery."""
await self.async_set_unique_id(format_mac(discovery_info.macaddress))
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.ip})
try:
box_name, _ = await self._validate_input(discovery_info.ip)
except DucoConnectionError:
return self.async_abort(reason="cannot_connect")
except DucoError:
_LOGGER.exception("Unexpected error discovering Duco box via DHCP")
return self.async_abort(reason="unknown")
self._host = discovery_info.ip
self._box_name = box_name
self.context["title_placeholders"] = {"name": box_name}
return await self.async_step_discovery_confirm()
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
+1 -6
View File
@@ -3,17 +3,12 @@
"name": "Duco",
"codeowners": ["@ronaldvdmeer"],
"config_flow": true,
"dhcp": [
{
"hostname": "duco_[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]"
}
],
"documentation": "https://www.home-assistant.io/integrations/duco",
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["duco"],
"quality_scale": "platinum",
"requirements": ["python-duco-client==0.3.4"],
"requirements": ["python-duco-client==0.3.2"],
"zeroconf": [
{
"name": "duco [[][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][]].*",
@@ -1,5 +1,4 @@
"""The EARN-E P1 Meter integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -40,7 +40,5 @@ class DomainData:
@cache
def get(cls, hass: HomeAssistant) -> Self:
"""Get the global DomainData instance stored in hass.data."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
ret = hass.data[DOMAIN] = cls()
return ret
@@ -1,9 +1,4 @@
{
"common": {
"api_key": "Access token",
"api_key_description": "The access token for authenticating with Firefly III",
"verify_ssl_description": "Verify the SSL certificate of the Firefly III instance"
},
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
@@ -19,39 +14,39 @@
"step": {
"reauth_confirm": {
"data": {
"api_key": "[%key:component::firefly_iii::common::api_key%]"
"api_key": "[%key:common::config_flow::data::api_key%]"
},
"data_description": {
"api_key": "[%key:component::firefly_iii::common::api_key_description%]"
"api_key": "The new API access token for authenticating with Firefly III"
},
"description": "The access token for your Firefly III instance is invalid and needs to be updated. Go to **Options > Remote access and tokens**. Create a new **personal access token** and copy it (it will only display once)."
"description": "The access token for your Firefly III instance is invalid and needs to be updated. Go to **Options > Remote access and tokens**. Create a new personal access token and copy it (it will only display once)."
},
"reconfigure": {
"data": {
"api_key": "[%key:component::firefly_iii::common::api_key%]",
"api_key": "[%key:common::config_flow::data::api_key%]",
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"api_key": "[%key:component::firefly_iii::common::api_key_description%]",
"api_key": "[%key:component::firefly_iii::config::step::user::data_description::api_key%]",
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:component::firefly_iii::common::verify_ssl_description%]"
"verify_ssl": "[%key:component::firefly_iii::config::step::user::data_description::verify_ssl%]"
},
"description": "Use the following form to reconfigure your Firefly III instance.",
"title": "Reconfigure Firefly III Integration"
},
"user": {
"data": {
"api_key": "[%key:component::firefly_iii::common::api_key%]",
"api_key": "[%key:common::config_flow::data::api_key%]",
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"api_key": "[%key:component::firefly_iii::common::api_key_description%]",
"api_key": "The API key for authenticating with Firefly III",
"url": "[%key:common::config_flow::data::url%]",
"verify_ssl": "[%key:component::firefly_iii::common::verify_ssl_description%]"
"verify_ssl": "Verify the SSL certificate of the Firefly III instance"
},
"description": "You can create an access token in the Firefly III UI. Go to **Options > Remote access and tokens**. Create a new **personal access token** and copy it (it will only display once)."
"description": "You can create an API key in the Firefly III UI. Go to **Options > Remote access and tokens**. Create a new personal access token and copy it (it will only display once)."
}
}
},
@@ -1,5 +1,4 @@
"""The Flux LED/MagicLight integration discovery."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -2,14 +2,11 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
import logging
from typing import Any, Concatenate
from typing import Any
from afsapi import (
AFSAPI,
FSApiError,
FSConnectionError,
FSNotImplementedError,
PlayCaps,
@@ -27,7 +24,6 @@ from homeassistant.components.media_player import (
RepeatMode,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
@@ -39,37 +35,6 @@ from .const import DOMAIN, MEDIA_CONTENT_ID_PRESET
_LOGGER = logging.getLogger(__name__)
def fs_command_exception_wrap[
_AFSAPIDeviceT: AFSAPIDevice,
**_P,
_R,
](
func: Callable[Concatenate[_AFSAPIDeviceT, _P], Awaitable[_R]],
) -> Callable[Concatenate[_AFSAPIDeviceT, _P], Coroutine[Any, Any, _R]]:
"""Wrap command methods and map API exceptions to HA errors."""
@wraps(func)
async def _wrap(self: _AFSAPIDeviceT, *args: _P.args, **kwargs: _P.kwargs) -> _R:
try:
return await func(self, *args, **kwargs)
except FSConnectionError as err:
command = func.__name__.removeprefix("async_")
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="connection_error",
translation_placeholders={"command": command},
) from err
except FSApiError as err:
command = func.__name__.removeprefix("async_")
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="api_error",
translation_placeholders={"command": command, "message": str(err)},
) from err
return _wrap
async def async_setup_entry(
hass: HomeAssistant,
config_entry: FrontierSiliconConfigEntry,
@@ -307,17 +272,14 @@ class AFSAPIDevice(MediaPlayerEntity):
# Management actions
# power control
@fs_command_exception_wrap
async def async_turn_on(self) -> None:
"""Turn on the device."""
await self.fs_device.set_power(True)
@fs_command_exception_wrap
async def async_turn_off(self) -> None:
"""Turn off the device."""
await self.fs_device.set_power(False)
@fs_command_exception_wrap
async def async_media_play(self) -> None:
"""Send play command."""
if (await self.fs_device.get_play_state()) == PlayState.STOPPED:
@@ -327,54 +289,45 @@ class AFSAPIDevice(MediaPlayerEntity):
else:
await self.fs_device.play()
@fs_command_exception_wrap
async def async_media_pause(self) -> None:
"""Send pause command."""
await self.fs_device.pause()
@fs_command_exception_wrap
async def async_media_stop(self) -> None:
"""Send stop command."""
await self.fs_device.stop()
@fs_command_exception_wrap
async def async_media_previous_track(self) -> None:
"""Send previous track command (results in rewind)."""
await self.fs_device.rewind()
@fs_command_exception_wrap
async def async_media_next_track(self) -> None:
"""Send next track command (results in fast-forward)."""
await self.fs_device.forward()
@fs_command_exception_wrap
async def async_mute_volume(self, mute: bool) -> None:
"""Send mute command."""
await self.fs_device.set_mute(mute)
# volume
@fs_command_exception_wrap
async def async_volume_up(self) -> None:
"""Send volume up command."""
volume = await self.fs_device.get_volume()
volume = int(volume or 0) + 1
await self.fs_device.set_volume(min(volume, self._max_volume or 1))
@fs_command_exception_wrap
async def async_volume_down(self) -> None:
"""Send volume down command."""
volume = await self.fs_device.get_volume()
volume = int(volume or 0) - 1
await self.fs_device.set_volume(max(volume, 0))
@fs_command_exception_wrap
async def async_set_volume_level(self, volume: float) -> None:
"""Set volume command."""
if self._max_volume: # Can't do anything sensible if not set
volume = int(volume * self._max_volume)
await self.fs_device.set_volume(volume)
@fs_command_exception_wrap
async def async_select_source(self, source: str) -> None:
"""Select input source."""
await self.fs_device.set_power(True)
@@ -384,7 +337,6 @@ class AFSAPIDevice(MediaPlayerEntity):
):
await self.fs_device.set_mode(mode)
@fs_command_exception_wrap
async def async_select_sound_mode(self, sound_mode: str) -> None:
"""Select EQ Preset."""
if (
@@ -393,7 +345,6 @@ class AFSAPIDevice(MediaPlayerEntity):
):
await self.fs_device.set_eq_preset(mode)
@fs_command_exception_wrap
async def async_set_repeat(self, repeat: RepeatMode) -> None:
"""Set repeat mode."""
await self.fs_device.play_repeat(
@@ -404,12 +355,10 @@ class AFSAPIDevice(MediaPlayerEntity):
}.get(repeat, PlayRepeatMode.OFF)
)
@fs_command_exception_wrap
async def async_set_shuffle(self, shuffle: bool) -> None:
"""Set shuffle mode."""
await self.fs_device.set_play_shuffle(shuffle)
@fs_command_exception_wrap
async def async_media_seek(self, position: float) -> None:
"""Seek to a position in seconds."""
await self.fs_device.set_play_position(int(position * 1000))
@@ -425,7 +374,6 @@ class AFSAPIDevice(MediaPlayerEntity):
return await browse_node(self.fs_device, media_content_type, media_content_id)
@fs_command_exception_wrap
async def async_play_media(
self, media_type: MediaType | str, media_id: str, **kwargs: Any
) -> None:
@@ -33,13 +33,5 @@
}
}
}
},
"exceptions": {
"api_error": {
"message": "Failed to execute {command}: {message}"
},
"connection_error": {
"message": "Failed to execute {command}: could not connect to device"
}
}
}
@@ -8,6 +8,11 @@
options:
- all
- any
for:
required: true
default: 00:00:00
selector:
duration:
is_closed:
fields: *condition_common_fields
@@ -1,6 +1,7 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"condition_for_name": "For at least",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
@@ -10,6 +11,9 @@
"fields": {
"behavior": {
"name": "[%key:component::garage_door::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::garage_door::common::condition_for_name%]"
}
},
"name": "Garage door is closed"
@@ -19,6 +23,9 @@
"fields": {
"behavior": {
"name": "[%key:component::garage_door::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::garage_door::common::condition_for_name%]"
}
},
"name": "Garage door is open"
@@ -8,6 +8,11 @@
options:
- all
- any
for:
required: true
default: 00:00:00
selector:
duration:
is_closed:
fields: *condition_common_fields
@@ -1,6 +1,7 @@
{
"common": {
"condition_behavior_name": "Condition passes if",
"condition_for_name": "For at least",
"trigger_behavior_name": "Trigger when",
"trigger_for_name": "For at least"
},
@@ -10,6 +11,9 @@
"fields": {
"behavior": {
"name": "[%key:component::gate::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::gate::common::condition_for_name%]"
}
},
"name": "Gate is closed"
@@ -19,6 +23,9 @@
"fields": {
"behavior": {
"name": "[%key:component::gate::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::gate::common::condition_for_name%]"
}
},
"name": "Gate is open"
@@ -1,5 +1,4 @@
"""Support for Actions on Google Assistant Smart Home Control."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -21,8 +21,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the platform."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
yaml_config: ConfigType = hass.data[DOMAIN][DATA_CONFIG]
google_config = config_entry.runtime_data
@@ -54,8 +54,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoogleMailConfigEntry) -
Platform.NOTIFY,
DOMAIN,
{DATA_AUTH: auth, CONF_NAME: entry.title},
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN][DATA_HASS_CONFIG],
)
)
@@ -1,5 +1,4 @@
"""The Hisense AEH-W4A1 integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import ipaddress
import logging
@@ -1,5 +1,4 @@
"""Pyaehw4a1 platform to control of Hisense AEH-W4A1 Climate Devices."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
+36 -1
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Awaitable, Callable
from datetime import timedelta
from ipaddress import ip_address
import logging
import secrets
import time
@@ -23,14 +24,16 @@ from yarl import URL
from homeassistant.auth import jwt_wrapper
from homeassistant.auth.const import GROUP_ID_READ_ONLY
from homeassistant.auth.models import User
from homeassistant.components import websocket_api
from homeassistant.const import HASSIO_USER_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.http import current_request
from homeassistant.helpers.json import json_bytes
from homeassistant.helpers.network import is_cloud_connection
from homeassistant.helpers.storage import Store
from homeassistant.util.network import is_local
from .auth_util import async_user_not_allowed_do_auth
from .const import (
KEY_AUTHENTICATED,
KEY_HASS_REFRESH_TOKEN_ID,
@@ -96,6 +99,38 @@ def async_sign_path(
return f"{url.path}?{url.query_string}"
@callback
def async_user_not_allowed_do_auth(
hass: HomeAssistant, user: User, request: Request | None = None
) -> str | None:
"""Validate that user is not allowed to do auth things."""
if not user.is_active:
return "User is not active"
if not user.local_only:
return None
# User is marked as local only, check if they are allowed to do auth
if request is None:
request = current_request.get()
if not request:
return "No request available to validate local access"
if is_cloud_connection(hass):
return "User is local only"
try:
remote_address = ip_address(request.remote) # type: ignore[arg-type]
except ValueError:
return "Invalid remote IP"
if is_local(remote_address):
return None
return "User cannot authenticate remotely"
async def async_setup_auth( # noqa: C901
hass: HomeAssistant,
app: Application,
@@ -1,45 +0,0 @@
"""Auth utilities for the HTTP component."""
from __future__ import annotations
from ipaddress import ip_address
from aiohttp.web import Request
from homeassistant.auth.models import User
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.http import current_request
from homeassistant.helpers.network import is_cloud_connection
from homeassistant.util.network import is_local
@callback
def async_user_not_allowed_do_auth(
hass: HomeAssistant, user: User, request: Request | None = None
) -> str | None:
"""Validate that user is not allowed to do auth things."""
if not user.is_active:
return "User is not active"
if not user.local_only:
return None
# User is marked as local only, check if they are allowed to do auth
if request is None:
request = current_request.get()
if not request:
return "No request available to validate local access"
if is_cloud_connection(hass):
return "User is local only"
try:
remote_address = ip_address(request.remote) # type: ignore[arg-type]
except ValueError:
return "Invalid remote IP"
if is_local(remote_address):
return None
return "User cannot authenticate remotely"
@@ -1,5 +1,4 @@
"""Support for Huawei LTE routers."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -34,8 +34,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up from config entry."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[config_entry.entry_id]
entities: list[Entity] = []
@@ -28,8 +28,6 @@ async def async_setup_entry(
async_add_entities: entity_platform.AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Huawei LTE buttons."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[config_entry.entry_id]
buttons = [
ClearTrafficStatisticsButton(router),
@@ -58,8 +58,6 @@ async def async_setup_entry(
# Grab hosts list once to examine whether the initial fetch has got some data for
# us, i.e. if wlan host list is supported. Only set up a subscription and proceed
# with adding and tracking entities if it is.
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[config_entry.entry_id]
if (hosts := _get_hosts(router, True)) is None:
return
@@ -27,8 +27,6 @@ async def async_get_service(
if discovery_info is None:
return None
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[discovery_info[ATTR_CONFIG_ENTRY_ID]]
default_targets = discovery_info[CONF_RECIPIENT] or []
@@ -40,8 +40,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up from config entry."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[config_entry.entry_id]
selects: list[Entity] = []
@@ -799,8 +799,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up from config entry."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[config_entry.entry_id]
sensors: list[Entity] = []
for key in SENSOR_KEYS:
@@ -31,8 +31,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up from config entry."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
router = hass.data[DOMAIN].routers[config_entry.entry_id]
switches: list[Entity] = []
+1 -21
View File
@@ -494,7 +494,6 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
async def _async_wait_push_loop(self) -> None:
"""Wait for data push from server."""
idle: asyncio.Future | None = None
while True:
try:
self.number_of_messages = await self._async_fetch_number_of_messages()
@@ -528,9 +527,8 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
else:
self.auth_errors = 0
self.async_set_updated_data(self.number_of_messages)
try:
idle = await self.imap_client.idle_start()
idle: asyncio.Future = await self.imap_client.idle_start()
await self.imap_client.wait_server_push()
self.imap_client.idle_done()
async with asyncio.timeout(10):
@@ -545,24 +543,6 @@ class ImapPushDataUpdateCoordinator(ImapDataUpdateCoordinator):
await self._cleanup()
await asyncio.sleep(BACKOFF_TIME)
finally:
# Ensure no pending IDLE future survives
if idle is not None and not idle.done():
idle.cancel()
_LOGGER.debug(
"Canceling IDLE wait for %s",
self.config_entry.data[CONF_SERVER],
)
try:
await idle
except asyncio.CancelledError:
if (
current_task := asyncio.current_task()
) and current_task.cancelling():
raise
except AioImapException:
pass
async def shutdown(self, *_: Any) -> None:
"""Close resources."""
if self._push_wait_task:
@@ -1,5 +1,4 @@
"""Support for INSTEON Modems (PLM and Hub)."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from contextlib import suppress
import logging
-1
View File
@@ -1,5 +1,4 @@
"""Native Home Assistant iOS app component."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import datetime
from http import HTTPStatus
@@ -88,8 +88,6 @@ async def async_unload_entry(
def async_add_defaults(hass: HomeAssistant, entry: KeeneticConfigEntry):
"""Populate default options."""
host: str = entry.data[CONF_HOST]
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
imported_options: dict = hass.data[DOMAIN].get(f"imported_options_{host}", {})
options = {
CONF_SCAN_INTERVAL: DEFAULT_SCAN_INTERVAL,
@@ -1,5 +1,4 @@
"""Support for Konnected devices."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import copy
import hmac
@@ -24,8 +24,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up binary sensors attached to a Konnected device from a config entry."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
data = hass.data[DOMAIN]
device_id = config_entry.data["id"]
sensors = [
@@ -1,5 +1,4 @@
"""Support for Konnected devices."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import asyncio
import logging
@@ -46,8 +46,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up sensors attached to a Konnected device from a config entry."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
data = hass.data[DOMAIN]
device_id = config_entry.data["id"]
@@ -1,5 +1,4 @@
"""Support for wired switches attached to a Konnected device."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import logging
from typing import Any
@@ -1,5 +1,4 @@
"""The kraken integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -149,8 +149,6 @@ async def async_setup_entry(
entities.extend(
[
KrakenSensor(
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN],
tracked_asset_pair,
description,
@@ -12,5 +12,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["thinqconnect"],
"requirements": ["thinqconnect==1.0.12"]
"requirements": ["thinqconnect==1.0.11"]
}
@@ -1,5 +1,4 @@
"""Support for LinkPlay devices."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from dataclasses import dataclass
@@ -1,5 +1,4 @@
"""Support for LinkPlay media players."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Utilities for the LinkPlay component."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from aiohttp import ClientSession
from linkplay.utils import async_create_unverified_client_session
@@ -113,8 +113,6 @@ async def handle_webhook(
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Configure based on config entry."""
if DOMAIN not in hass.data:
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN] = {"devices": set(), "unsub_device_tracker": {}}
webhook.async_register(
hass, DOMAIN, "Locative", entry.data[CONF_WEBHOOK_ID], handle_webhook
@@ -1,5 +1,4 @@
"""Support for the Locative platform."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from homeassistant.components.device_tracker import TrackerEntity
from homeassistant.config_entries import ConfigEntry
@@ -1,5 +1,4 @@
"""Support for Mailgun."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import hashlib
import hmac
@@ -44,8 +44,6 @@ def get_service(
discovery_info: DiscoveryInfoType | None = None,
) -> MailgunNotificationService | None:
"""Get the Mailgun notification service."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
data = hass.data[DOMAIN]
mailgun_service = MailgunNotificationService(
data.get(CONF_DOMAIN),
@@ -1,5 +1,4 @@
"""The Matter integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -37,8 +37,6 @@ def get_matter(hass: HomeAssistant) -> MatterAdapter:
# NOTE: This assumes only one Matter connection/fabric can exist.
# Shall we support connecting to multiple servers in the client or by
# config entries? In case of the config entry we need to fix this.
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
matter_entry_data: MatterEntryData = next(iter(hass.data[DOMAIN].values()))
return matter_entry_data.adapter
@@ -1,5 +1,4 @@
"""Support for Meteo-France weather data."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import logging
@@ -58,8 +58,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
await data_coordinator.async_config_entry_first_refresh()
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN][conn_type][key] = data_coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
-1
View File
@@ -1,5 +1,4 @@
"""Support for mill wifi-enabled home heaters."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from typing import Any
-2
View File
@@ -22,8 +22,6 @@ async def async_setup_entry(
) -> None:
"""Set up the Mill Number."""
if entry.data.get(CONNECTION_TYPE) == CLOUD:
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
mill_data_coordinator: MillDataUpdateCoordinator = hass.data[DOMAIN][CLOUD][
entry.data[CONF_USERNAME]
]
-1
View File
@@ -1,5 +1,4 @@
"""Support for mill wifi-enabled home heaters."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Integrates Native Apps to Home Assistant."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from contextlib import suppress
from functools import partial
@@ -110,8 +110,6 @@ class MobileAppEntity(RestoreEntity):
def _apply_pending_update(self) -> None:
"""Restore any pending update for this entity."""
entity_type = self._config[ATTR_SENSOR_TYPE]
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
pending_updates = self.hass.data[DOMAIN][DATA_PENDING_UPDATES][entity_type]
if update := pending_updates.pop(self._attr_unique_id, None):
_LOGGER.debug(
@@ -170,8 +170,6 @@ def safe_registration(registration: dict) -> dict:
def savable_state(hass: HomeAssistant) -> dict:
"""Return a clean object containing things that should be saved."""
return {
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
DATA_DELETED_IDS: hass.data[DOMAIN][DATA_DELETED_IDS],
}
@@ -1,5 +1,4 @@
"""Support for mobile_app push notifications."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Mobile app utility functions."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Webhook handlers for mobile_app."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Mobile app websocket API."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""The motion_blinds component."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
import asyncio
import logging
@@ -15,8 +15,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = MullvadCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
@@ -29,8 +29,6 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Defer sensor setup to the shared sensor module."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
coordinator = hass.data[DOMAIN]
async_add_entities(
@@ -1,5 +1,4 @@
"""Connect to a MySensors gateway via pymysensors API."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Handle MySensors devices."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -284,8 +284,6 @@ async def _gw_start(
gateway.on_conn_made = gateway_connected
# Don't use hass.async_create_task to avoid holding up setup indefinitely.
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN][MYSENSORS_GATEWAY_START_TASK.format(entry.entry_id)] = (
asyncio.create_task(gateway.start())
) # store the connect task so it can be cancelled in gw_stop
@@ -62,8 +62,6 @@ def discover_mysensors_node(
hass: HomeAssistant, gateway_id: GatewayId, node_id: int
) -> None:
"""Discover a MySensors node."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
discovered_nodes = hass.data[DOMAIN].setdefault(
MYSENSORS_DISCOVERED_NODES.format(gateway_id), set()
)
@@ -230,8 +230,6 @@ async def async_setup_entry(
"""Add battery sensor for each MySensors node."""
gateway_id = discovery_info[ATTR_GATEWAY_ID]
node_id = discovery_info[ATTR_NODE_ID]
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
gateway: BaseAsyncGateway = hass.data[DOMAIN][MYSENSORS_GATEWAYS][gateway_id]
async_add_entities([MyBatterySensor(gateway_id, gateway, node_id)])
@@ -61,8 +61,6 @@ MAX_WEBHOOK_RETRIES = 3
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Netatmo component."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
hass.data[DOMAIN] = {
DATA_PERSONS: {},
DATA_DEVICE_IDS: {},
@@ -1,5 +1,4 @@
"""Support for the Netatmo cameras."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""Support for Netatmo Smart thermostats."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -1,5 +1,4 @@
"""The Netatmo data handler."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -140,8 +140,6 @@ class NetatmoRoomEntity(NetatmoDeviceEntity):
if device := registry.async_get_device(
identifiers={(DOMAIN, self.device.entity_id)}
):
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
self.hass.data[DOMAIN][DATA_DEVICE_IDS][self.device.entity_id] = device.id
@property

Some files were not shown because too many files have changed in this diff Show More